Codex Desktop `/goal` ignores auto-created `Pasted text.txt` attachments and treats the goal as empty
Summary
Codex Desktop converts sufficiently large pasted text into a text-file attachment named Pasted text.txt. That attachment is non-empty on disk and is visible in the composer, but the /goal submit path derives the goal objective only from the visible editor text.
As a result, if the user intends the attached pasted text to be the /goal objective and does not also type the same content into the editor, Codex Desktop handles the command as an empty or missing goal objective. The failure looks like a blank submission even though the composer has a non-empty text attachment.
This is not a model issue and not a backend interpretation issue. In the inspected Desktop build, the client stores pasted text as fileAttachments, while the /goal objective extraction path reads only composer.getText() / promptRaw and never materializes the text-file attachment into the objective before validation.
Environment
- Product: Codex Desktop
- OS: Windows 11 Pro, x64, build 26200
- Codex Desktop package:
OpenAI.Codex_26.527.3686.0_x64__2p2nqsd0c76g0 - Codex Desktop version:
26.527.3686.0 - Installed app bundle SHA-256 for
app.asar:51D9C84D8C51D51A71E1EA05FA0521DB3225E68914FED680CD64960A4B7A745A - Public repository revision inspected for protocol/TUI comparison:
openai/codex@3e7baa00e43419967d90d6ad9cef40f58d5ac89f - Date observed: 2026-05-30 / 2026-05-31 JST
Reproduction
- Open Codex Desktop on Windows.
- Start a thread or open an existing thread.
- Paste a long non-empty text block into the composer, large enough for Desktop to convert it into a text attachment.
- In this build, the UI/RPC path labels this file as
Pasted text.txt.
- Use
/goalor activate goal mode with the pasted text attachment intended to be the goal objective. - Do not manually type the full pasted text into the visible editor. The visible editor text is empty or contains only the
/goalcommand/prefix. - Submit.
Expected behavior
Codex should not treat a non-empty pasted-text attachment as an empty goal submission.
One of these behaviors would be acceptable:
- Expand/materialize the
Pasted text.txtpayload into the/goalobjective before goal validation, the same way the TUI expands pending large-paste placeholders before submit and before/goallength validation. - If the expanded objective exceeds the goal length limit, show the existing "goal too long" style validation error. It should not be reported or handled as empty.
- If file/text attachments are intentionally unsupported as
/goalobjective input, block the submit with a specific UI error such as "Text attachments cannot be used as a goal objective; type a short goal or refer to the file by name/path." It should not silently behave like a blank objective.
This report is not asking for /goal to bypass the objective length limit. It is asking for non-empty pasted text not to disappear from the goal-validation path as if it were whitespace.
Actual behavior
The composer has a non-empty Pasted text.txt attachment, but /goal behaves as though the objective is empty or missing when the visible editor text is empty/only the command prefix.
Local state confirmed that the attachments were real files and not empty UI shells. The Desktop app recorded pasted-text attachment paths under:
%USERPROFILE%\.codex\attachments\pasted-text-attachments.json
The recorded attachment files existed and had non-zero sizes, for example:
f19a18da-088e-4280-84b6-fdc3c940f3b9\pasted-text.txt 14963 bytes
f0a64c46-0e40-4d2c-abe9-fef76796360a\pasted-text.txt 11012 bytes
8adae7d1-9b28-418e-8622-847e10660ab6\pasted-text.txt 14963 bytes
5282069a-3f13-4dbb-8119-bf4a2d372c3a\pasted-text.txt 28029 bytes
e696cf76-45c1-48d1-8107-28df2368fe9b\pasted-text.txt 10161 bytes
The problem is therefore not that the pasted text was empty, missing on disk, or failed to be written. The problem is that the Desktop /goal submit path does not read that attachment when computing the goal objective.
Root cause confirmed in Codex Desktop 26.527.3686.0
I inspected the installed Desktop webview bundle from the package listed above. The identifiers below are from the minified bundle, so function names may not be stable between builds, but the data flow is clear in this build.
Desktop bundle evidence locations:
composer-Beu4UAMC.js: pasted-text attachment creation, submit-block predicate,/goalobjective handling, and local turn start-params construction.app-server-manager-signals-Bpaj8VHp.js: prompt/context text construction and user-message display parsing.composer-view-state-CuoA48W8.js: persisted composer state shape, includingpendingThreadGoalObjectiveandfileAttachments.build-start-conversation-params-NLWgpXGB.js:inputandattachmentsare kept as separate fields when building thread-start params.
1. Large pasted text is converted into a file attachment
In composer-Beu4UAMC.js, the pasted-text handler creates a Pasted text.txt attachment through the RPC method:
create-pasted-text-attachment-for-host
The handler then appends the returned object to fileAttachments. Removal uses:
remove-pasted-text-attachment-for-host
This means Desktop does not keep the full large paste as ordinary editor text. It writes the paste as an attachment and tracks it in composer attachment state.
In app-server-manager-signals-Bpaj8VHp.js, the pasted-text attachment manager writes real files named pasted-text.txt under the Codex attachments directory and records them in pasted-text-attachments.json. The bundle contains the constants and registry shape:
pasted-text.txt
pasted-text-attachments.json
attachmentPaths
pendingRemovalPaths
The local files I inspected were non-empty UTF-8 text files, so the bug is not an attachment-write failure or empty file.
2. The normal submit-block predicate counts fileAttachments as message content
The same Desktop composer computes a message-content predicate from multiple sources. Completed fileAttachments are included in that predicate, while pending file creation blocks submission as file-uploads.
That explains the confusing UI state: the composer can visibly contain content and may be considered non-empty for the general submit button because a file attachment exists.
However, "has message content" is not the same as "has a /goal objective." The goal path below uses a separate objective extraction step.
3. /goal objective extraction only reads the visible editor text
The Desktop submit handler computes the raw prompt from the editor text, effectively:
promptRaw = composer.getText()
It then calls the goal handling function with that promptRaw. The goal function:
- extracts the objective from
/goal ...text when the slash command is present, - falls back to the raw editor text when goal mode is active,
- trims the result,
- treats zero-length output as an empty goal objective,
- sets
pendingThreadGoalObjectiveto an empty string and clears the editor in that empty-objective branch.
There is no corresponding step that reads fileAttachments, opens Pasted text.txt, or expands that attachment into promptRaw before goal parsing.
Therefore, when the visible editor text is empty or only /goal without a typed argument, the objective is empty even if the attached pasted text file contains thousands of characters.
4. Turn construction keeps prompt text and file attachments separate
The Desktop start-params builder creates a text input from the prompt string and sends file attachments separately. In the inspected bundle, the shape is:
input: [{ type: "text", text: prompt, text_elements: [] }, ...images]
fileAttachments: context.fileAttachments
The final conversation params builder then maps fileAttachments / added files into the separate attachments field, while leaving input unchanged:
input: input
attachments: attachmentsFrom([...fileAttachments, ...addedFiles])
The prompt-context builder lists attachments under a metadata section titled:
# Files mentioned by the user:
It records attachment labels and paths there, but it does not inline the contents of Pasted text.txt into the prompt text or into threadGoalObjective.
So even after submit, the pasted text attachment is metadata/attachment context, not the actual /goal objective string.
The public goal API also expects the goal objective as a string field, not as an attachment. ThreadGoalSetParams has objective: Option<String>, and the app-server processor trims and validates that string before setting the goal:
codex-rs/app-server-protocol/src/protocol/v2/thread.rslines 740-757codex-rs/app-server/src/request_processors/thread_goal_processor.rslines 137-142codex-rs/protocol/src/protocol.rslines 3562-3573
5. Public app-server protocol has text/image inputs, not a generic text-attachment input
In the public repository, turn/start documents the turn input list as text or image input:
codex-rs/app-server/README.mdlines 648-654:{"type":"text","text":"Explain this diff"}{"type":"image","url":"https://...png"}{"type":"localImage","path":"/tmp/screenshot.png"}
The public v2 protocol enum similarly contains:
TextImageLocalImageSkillMention
See codex-rs/app-server-protocol/src/protocol/v2/turn.rs lines 268-298.
I did not find a file, textAttachment, or pastedTextAttachment user-input variant in that public protocol enum. That makes it especially important for the Desktop client to decide whether a pasted-text attachment is:
- expanded into normal
Textinput, - referenced explicitly by path/name in the user's text,
- or rejected with a targeted UI message for goal-objective use.
Currently, the Desktop /goal path does none of those; it just computes the objective from visible editor text.
Contrast with the public TUI behavior
The public TUI implementation already handles the same conceptual problem differently and correctly for large paste placeholders.
In codex-rs/tui/src/bottom_pane/chat_composer.rs:
- Large pastes insert a placeholder element into the text buffer.
- The full paste is stored in
pending_pastes. - Before submit,
prepare_submission_text_with_optionsexpands pending paste placeholders back into the full text.
Relevant public source locations at openai/codex@3e7baa00e43419967d90d6ad9cef40f58d5ac89f:
chat_composer.rslines 62-70 describe large paste placeholders andpending_pastes.chat_composer.rslines 875-889 insert the placeholder and store the full pasted text.chat_composer.rslines 2152-2195 implement pending-paste expansion.chat_composer.rslines 2655-2660 expand pending pastes before submission.
The TUI /goal path also explicitly counts the expanded pending-paste text before validating the goal length:
There is also a regression test in the public TUI for this exact invariant:
So the public TUI has a clear invariant: a large paste may be represented indirectly in the editor, but it is expanded before submit/goal validation. Codex Desktop breaks that invariant by storing the paste as fileAttachments and not expanding it into the /goal objective path.
Related issue
Related but not duplicate: #25144 asks for an option to disable automatic conversion of long pasted prompts into .txt attachments. This issue is narrower and behavioral: once Codex Desktop has already converted a large paste into Pasted text.txt, that non-empty text must not be treated as an empty /goal objective without a specific error.
Why this matters
- The UI shows non-empty user content, but the command path behaves like the input is blank.
- Long goals/prompts are exactly the case where users are most likely to hit automatic pasted-text attachment conversion.
- The failure mode gives the wrong diagnosis. It looks like the user submitted whitespace, but the pasted text exists on disk and is attached.
- Users may repeatedly retry or lose time because no message explains that
Pasted text.txtis not considered a/goalobjective. - It creates inconsistent behavior between Codex clients: the public TUI expands pending large-paste content for
/goal, while Desktop does not.
Suggested fix
Please make the Desktop /goal path use a single explicit materialization step before goal validation:
- Start from visible editor text.
- Expand any pending large-paste placeholders.
- For Desktop
Pasted text.txtattachments created bycreate-pasted-text-attachment-for-host, either:
- read and append/materialize the text into the candidate goal objective, then run the normal length validation, or
- reject it before submission with a specific "text attachments cannot be used as goal objectives" error.
- Only after that should the code decide whether the objective is empty, too long, or valid.
More generally, Desktop should separate these predicates:
hasComposerContentForSubmitButtonhasMaterializedPromptTextForTurnhasGoalObjectiveForGoalCommand
Right now a file attachment can satisfy the first predicate while being invisible to the third predicate.
Suggested regression tests
- Paste long non-empty text that Desktop converts into
Pasted text.txt, enter/goalwith no typed argument, and submit.
- Expected: either the attached text is materialized and validated, or a specific unsupported-attachment error appears.
- Not expected: empty-objective handling.
- Repeat with attached pasted text below the goal character limit.
- Expected if materialization is supported: goal is set to the pasted text.
- Repeat with attached pasted text over the goal character limit.
- Expected if materialization is supported: goal-length validation error.
- Not expected: empty-message / empty-goal handling.
- Attach whitespace-only pasted text.
- Expected: still rejected as empty after materialization/trim.
- Attach a normal non-text file and use
/goalwith no typed objective.
- Expected: targeted unsupported-attachment message, not silent empty behavior.
- Verify normal message submission still treats file attachments according to the intended attachment semantics.
- Verify image-only and text-plus-image submission behavior is unchanged.
Workaround
For now, the reliable workaround is to type a short goal objective directly in the editor and refer to the attached file by name/path, for example:
/goal follow the instructions in the attached Pasted text.txt
That workaround is not obvious from the UI, and it does not fix the core issue that the automatic pasted-text attachment is silently ignored by the /goal objective extractor.
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Thanks for the duplicate check.
I reviewed #25199. It is related, but I do not think this issue is a duplicate.
#25199 is about the VS Code IDE extension converting pasted task cards into
Pasted text.txtwith no editable inline-text escape hatch, causing task/session naming and workflow issues.This issue is narrower: in Codex Desktop, an auto-created non-empty
Pasted text.txtattachment is counted as composer content, but the/goalobjective path reads only the visible editor text /promptRaw. As a result, the goal objective is treated as empty even though the pasted-text attachment exists on disk and is non-empty.I am happy to provide more diagnostics, test a candidate build, or contribute a targeted PR if the Codex team thinks an external PR would be useful for this specific path.
This is a major issue for Codex on Windows and very inconvenient for me.
I’d like to add another affected use case here, because this is causing a complete workflow breakage when Codex Desktop is used together with skills/custom instructions that expect the user’s pasted task description to be part of the actual chat input.
Current behavior:
When I paste a large prompt/task card into Codex Desktop on Windows, the app automatically converts it into a
Pasted text.txtattachment instead of inserting the text into the chat composer.The problem is not only that the text is no longer editable or visible in the composer. In some workflows, Codex appears to completely ignore the attached
Pasted text.txt, especially when the instruction is meant to be consumed by a skill or by a mode that reads the visible user message as the main task input.So the final result is:
.txtattachment.This makes long prompt/task-card workflows almost unusable. The only reliable workaround I have found is to paste the instruction in small chunks and manually reconstruct it in the chat, which is very inefficient and error-prone.
Could the team please consider one of these fixes?
.txtattachments.Pasted text.txtattachments are always treated as first-class user input by all modes/skills, exactly as if the user had pasted the text directly into the chat.The current behavior is especially problematic because the UI makes it look like the pasted content was included, but the agent may ignore it completely depending on the workflow. That is much worse than a visible paste limit, because it creates silent task failures.
Thanks for looking into this. A toggle to keep the old paste behavior would already solve the issue for users who rely on long structured prompts.
They fixed adding the same system they use in the site, ok, that will work.
But now I'm getting "The model 'gpt-image-2' does not exist." all the time, it just breaks the work he is doing.
Update after the latest Windows Desktop build:
I rechecked this on
OpenAI.Codex_26.601.2237.0_x64__2p2nqsd0c76g0on Windows. The installedapp.asarSHA-256 is0084A0580500CD33E48DF2C96A5FE4B77620732CC757F9B2234C92BBCDB45A05.The general pasted-attachment behavior does appear to have changed in the current bundle, consistent with the update described in #25144. The prompt-construction path now detects the specific case where:
fileAttachmentscontains one of the generated pasted-text attachment paths.In that case it adds an explicit instruction before
## My request for Codex:saying that the attached pasted text files contain the user's request and should be read/acted on. That looks like it should fix the normal "attachment-only paste is treated as reference material" path.However, this issue is about the narrower
/goalobjective path, and that still appears to be a separate path in the current build.From the current webview bundle:
promptRawfrom the visible composer text (X.getText())./goalhandling function receives thatpromptRawand extracts the goal objective from it.fileAttachments,generatedPastedTextAttachmentPaths, andpastedTextRestores, but those are used for the turn input/context. The goal objective is passed separately asthreadGoalObjective.Pasted text.txt, the goal handler can still take the empty visible string as the objective and go down the empty-objective branch. It does not appear to materialize the generated pasted attachment into the goal objective before validating/setting the goal.So my current read is:
/goal-specific issue can still remain if the goal objective exists only as an auto-created pasted-text attachment.The likely fix would be to make the
/goalobjective extraction use the same "attachment-only paste is the actual request" materialization path before validating the goal objective, or to avoid treating an empty visible editor as an empty goal when a generated pasted-text attachment is present.I have not posted a new UI repro video for this build yet, but the current bundle structure strongly suggests the generic paste fix and the
/goalobjective extraction are still separate code paths. I can test a candidate build or provide a focused repro pack if helpful.