Skip to content

fix(agents): a codex agent starts in a workspace it has never seen - #152

Open
rogeriochaves wants to merge 2 commits into
mainfrom
fix/codex-directory-trust
Open

fix(agents): a codex agent starts in a workspace it has never seen#152
rogeriochaves wants to merge 2 commits into
mainfrom
fix/codex-directory-trust

Conversation

@rogeriochaves

@rogeriochaves rogeriochaves commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

What

Codex asks about a directory the first time it starts in one, and waits on the answer before it draws anything:

  Do you trust the contents of this directory? ...
› 1. Yes, continue
  2. No, quit

A headless agent has nobody to answer. So the very first launch in a fresh workspace sat there until a person opened the pane: the tmux session was up, the agent was not, and #151 correctly declined to name a session it could not reach.

The answer, written before the process starts

The answer codex records by hand is a [projects."<path>"] table in config.toml. The launcher now writes it up front, through a new prepareWorkspace entry in the runtime table. Before the process starts, not after: a question asked on the way up is one nothing after the launch can answer.

It grants codex nothing it does not already have here. Agents launch with --dangerously-bypass-approvals-and-sandbox, so the sandbox this question guards is already off for them.

Claude asks nothing on its way up, so nothing is written for it.

What it will not do

  • An answer the operator already gave is kept, trusted or not. Only a directory codex has no entry for gets one, so a deliberate "no" is never undone.
  • The table is appended whole at the end of the file, so nothing above it is reread as belonging to it and a marker-managed block keeps its shape.
  • A config that cannot be written costs the launch its first frame, not the launch.

Checked end to end

A real codex, launched through ensureAgentSession into a directory it had never seen:

trusted before? 0
action: launched | named: true | waited: 2759 ms
• Session renamed to pr-reviewer. To resume this session run codex resume,
  then select pr-reviewer (01a02d26-11f2-7b30-a29b-eac4d2ad4c0b)
› Ask Codex to do anything

No question, and the name from #151 lands on the first launch instead of the second.

Tests

Six, covering the two scenarios added to specs/sessions/headless-reconcile.feature:

  • records the workspace as trusted, in the table codex writes itself
  • keeps what the config already held, and starts its own table
  • leaves an answer the operator already gave
  • writes one entry however many times a workspace is launched
  • quotes a path that would otherwise break the table header
  • codex settles its workspace, claude has nothing to settle

Gate: 338 tests pass, tsc --noEmit clean.


Second commit: reading the composer before deciding a name was taken

Deploying #151 to the agents box found a real fault in it. The launcher typed /rename pr-reviewer into a live codex, pressed Enter, reported the session named, and left the command sitting unrun in the composer of a running agent:

› /rename pr-reviewer
  gpt-5.6-terra high · ~/agent-workspaces/pr-reviewer

Two faults, one after the other.

The check read the pane straight after the Enter. A pane that has not drawn the command yet looks exactly like one that has already run it, so the very check meant to catch an unsubmitted command was what passed it. The composer is now read until the command shows up in it, before any Enter, and only then is its disappearance an answer. A runtime that never draws the keystrokes reports the session unnamed, which is what it is.

The Enter itself was dropped. A codex still loading a resumed session swallows it with no sign. It is now repeated until the composer clears, up to four times. An Enter on a composer the runtime has since cleared does nothing, so a retry costs a keystroke and never a second command, and the command is typed exactly once however many Enters it takes.

Removing the wait-for-drawn gate fails the new test with the box's own symptom, and nothing else.

Checked on the box

Same branch built and deployed there, then the codex agent relaunched through kanban reconcile with no hand-holding:

pr-reviewer: resumed [langwatch, langwatch-saas, scenario]
• Session renamed to pr-reviewer. To resume this session run codex resume,
  then select pr-reviewer (019fc7a9-fc86-7f02-808d-70d67b46d92e)
› Ask Codex to do anything

Codex's index gained the entry on its own, and the composer is clear.

Four more tests: waits for its own keystrokes to be drawn, presses Enter again for a runtime that dropped the first, gives up rather than pressing Enter forever, and never types the command twice. 341 tests pass, tsc --noEmit clean.

Codex asks about a directory the first time it starts in one, and waits on
the answer before it draws anything at all. A headless agent has nobody to
answer, so its very first launch in a fresh workspace sat on the question
until a person opened the pane. The session was up, the agent was not.

The answer is a `[projects."<path>"]` table in config.toml, which is what
codex writes once the question is answered by hand. The launcher now records
it before it starts the process, since a question asked on the way up is one
nothing after the launch can answer. It grants codex nothing it does not
already have here: agents launch with
`--dangerously-bypass-approvals-and-sandbox`, so the sandbox the question
guards is already off for them.

A directory the operator has already ruled on is left alone, trusted or not:
only one codex has no entry for gets one. The table is appended whole, so
nothing above it is reread as belonging to it, and a marker-managed block
keeps its shape.

Claude asks nothing on its way up, so nothing is written for it.

Claude-Session: https://claude.ai/code/session_01BUKKUiZbSBHmrLK9JJj4Ba
On a box, the launcher typed `/rename pr-reviewer` into a real codex, pressed
Enter, reported the session named, and left the command sitting unrun in the
composer of a live agent. Two faults, one after the other.

The check read the pane straight after the Enter. A pane that has not drawn
the command yet looks exactly like one that has already run it, so the very
check meant to catch an unsubmitted command was what passed it. The composer
is now read until the command shows up in it, before any Enter, and only then
is its disappearance an answer. A runtime that never draws the keystrokes
reports the session unnamed, which is what it is.

The Enter itself is dropped by a codex still loading a resumed session. It is
now repeated until the composer clears, up to four times. An Enter on a
composer the runtime has since cleared does nothing, so a retry costs a
keystroke and never a second command, and the command is typed exactly once
however many Enters it takes.

Removing the wait-for-drawn gate fails the new test with the box's own
symptom, and nothing else.

Claude-Session: https://claude.ai/code/session_01BUKKUiZbSBHmrLK9JJj4Ba
@rogeriochaves
rogeriochaves force-pushed the fix/codex-directory-trust branch from a49ecaf to 4fbeb39 Compare August 25, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant