Repository navigation
Description
Expected behavior
Clicking "trust directory" on the Codex trust modal writes the accept keystroke to the Codex PTY, the dialog resolves, and trust persists. If the write cannot be delivered, the user sees why.
Actual behavior
Clicking (even spamming) the button does nothing visible. The dialog stays up, the pane never becomes usable, and because the modal is application-global (#713) the whole app is unusable while it shows.
Root cause (two stacked defects)
- The refusal is invisible.
sendConditionKey(src/renderer/src/workspace/tile-tree/TileLeaf.tsx:399-418) correctly surfaces failures —!runtime.conditions→ "That prompt is no longer live.",sendInput === false→ "That keystroke did not reach the agent…" — but as pane toasts, which render underneath the Radix app-modal overlay the trust dialog itself creates. Every refusal is therefore silent in exactly the situation where it matters most. - The write is refused.
SessionManager.write()(src/main/sessionManager.ts:3293+) returnsfalsebeforerecordInputWritewhen no live registry entry exists or a delivery holds the reservation. In the recorded incident (app run2026-08-31T00-48-19-592Z-main-63968-59a3af, codex session9d9fd575…inbringdown), the user's clicks produced zeroinput.writejournal rows — the writes were refused at main (or fenced at!runtime.conditionson a stale frozen snapshot; TileLeaf's own quarantine comment documents that a fenced pane's "last condition snapshot stays frozen on screen while main's entry under that id may belong to someone else"). The session was meanwhile cycling through the bug(session): wake readiness timeout kills a live codex blocked on its trust dialog — 30s kill loop #712 kill loop, so for most of the spamming window there was no live backend to write to.
Note the keystroke contract itself is NOT the bug this time: vendored codex source (vendor/codex-src/codex-rs/tui/src/onboarding/trust_directory.rs:122) still maps Char('1') | Char('y') → trust, matching CODEX_TRUST_DIALOG_ACCEPT_KEYS = '1' (codex-cli 0.151.0 in use).
Impact
An untrusted folder makes a Codex pane — and with #712, the whole app — unusable, with zero feedback. Same severity class as the Claude trust regression (#705), different mechanism.
Fix direction
- Refusal feedback must be visible above a modal: surface condition-keystroke failures inside the modal itself (or via the global toast layer that stacks above dialogs), not as a pane toast.
- A condition click on a session with no live backend should trigger the same-SessionId wake (as the composer now does, bug(session): prompts to restored sessions after app restart are rejected never-owned — no wake on the composer send path #706/fix(session): wake the missing backend before failing a composer delivery #708) or explicitly tell the user the pane is dead — never a silent no-op.
- Clear/refresh frozen condition snapshots when main has no entry for the session (the stale-modal half).
Environment
agent-code build 3378d171 (dirty), codex-cli 0.151.0, macOS. Related: #705 (Claude sibling), #712 (kill loop), #713 (app-global modal).
Activity
Juliusolsson05 commentedon Sep 20, 2026
Audit note (2026-09-20): the trigger is largely gone; both stated fix directions are still open, and one of them is now inconsistent with code that shipped today.
Shipped.
ed9e852f(Fixes #714) callsensureCodexProjectTrust(this.cwd)early inCodexSession.start(codexSession.ts:317-330), pre-writingtrust_level = "trusted"before the PTY spawns, so app-spawned Codex sessions in untrusted folders never paint the dialog. Ordering pinned bycodexSession.launchOrdering.test.ts:186. The "spamming window" half is gone too — see #712, now closed.Not shipped, item 1 — the refusal is still invisible above a modal.
sendConditionKey(TileLeaf.tsx:413-432) still reports both refusals throughworkspace.showPaneToast, which renders asPaneToast— an inline flex child of the pane, with no z-index, sitting under the Radix overlay atz-[1100](dialog.tsx:29) and inside the subtree Radix marksaria-hidden.This is now inconsistent within the same feature: #1070 (merged today as #1099) moved condition refusal messages onto
GlobalToastatz-[1200]for exactly this reason, andGlobalToast's own header records the trap. The pty arm thatsendConditionKeyowns did not move. So a refused structured answer is now visible and a refused keystroke answer is still not — and the keystroke arm is the one a trust dialog uses.Not shipped, item 2 — a click on a session with no live backend is still a silent no-op.
sendConditionKeydoes not callensureSessionLive, andSessionManager.write()(sessionManager.ts:3514-3575) returnsfalsebeforerecordInputWritewhen a delivery holds the reservation or there is no registry entry. The frozen-snapshot half (clear or refresh when main has no entry) is untouched; the quarantine reasoning is still in the comment atTileLeaf.tsx:400-412.Item 1 is now a small, well-defined change: route
sendConditionKey's two refusals through the same global toast the refusal reporter uses.Juliusolsson05 commentedon Sep 25, 2026
Reopened: #1110 closed this through its "Closes #711 (item 1)" trailer, but only item 1 shipped.
sendConditionKeydoesn't callensureSessionLive, andSessionManager.write()returnsfalsebeforerecordInputWritewhen there is no registry entry. The user now at least sees the refusal (item 1), but the pane doesn't wake. The frozen-snapshot half (clear or refresh the condition when main has no entry) is also untouched.