Skip to content

Commit 695b833

Browse files
gaoyu06claude
andcommitted
fix(codex): auto-approve in full-auto so a stale-policy turn can't hang
A codex turn bakes its approvalPolicy at turn/start, so a turn begun before a switch to full-auto (or the startup mode-sync race) could still emit a requestApproval. Unanswered — because the UI shows full-auto and the user isn't expecting a prompt — it blocked the turn forever: the command never completed (its card spun "running"), and the turn never ended ("generating"). Now, when the session is full-auto, the adapter auto-approves bridged approval requests (decision: accept), honoring the user's "no prompts" choice and unsticking the turn. Other modes still surface the approval card. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a3c92c2 commit 695b833

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

src/lib/backends/codex.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,37 @@ describe('codex adapter: rewind', () => {
6767
});
6868
});
6969

70+
describe('codex adapter: full-auto approvals', () => {
71+
it('auto-approves a bridged approval request so the turn cannot block', () => {
72+
const { lines } = makeIo();
73+
const adapter = createCodexAdapter();
74+
handshake(adapter, lines);
75+
adapter.encodeOp({ op: 'set_approval_mode', mode: 'full-auto' });
76+
lines.length = 0;
77+
const events = adapter.translate({
78+
id: 42,
79+
method: 'item/commandExecution/requestApproval',
80+
params: { itemId: 'i1', command: 'rm -rf x', reason: 'destructive' }
81+
});
82+
// No approval card surfaces; an accept response goes straight back.
83+
expect(events).toEqual([]);
84+
expect(parse(lines[0])).toMatchObject({ id: 42, result: { decision: 'accept' } });
85+
});
86+
87+
it('still surfaces an approval card in non-full-auto modes', () => {
88+
const { lines } = makeIo();
89+
const adapter = createCodexAdapter();
90+
handshake(adapter, lines);
91+
adapter.encodeOp({ op: 'set_approval_mode', mode: 'auto-edit' });
92+
const events = adapter.translate({
93+
id: 43,
94+
method: 'item/commandExecution/requestApproval',
95+
params: { itemId: 'i1', command: 'ls' }
96+
});
97+
expect(events[0]).toMatchObject({ type: 'approval_request', name: 'bash' });
98+
});
99+
});
100+
70101
describe('codex adapter: handshake', () => {
71102
it('onStart sends only the initialize request', () => {
72103
const { lines, io } = makeIo();

src/lib/backends/codex.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,19 @@ export function createCodexAdapter(): EngineAdapter {
628628
}
629629

630630
function onServerRequest(id: RequestId, method: string, params: unknown): NormalizedEvent[] {
631+
// full-auto means "no prompts". A turn bakes its approvalPolicy at turn/start,
632+
// so one started before a switch to full-auto (or the startup mode-sync race)
633+
// can still emit a requestApproval — which, unanswered, blocks the turn (the
634+
// command never completes, its card spins, the turn never ends). Auto-approve
635+
// here to honor the user's full-auto choice and unstick it.
636+
if (
637+
mode === 'full-auto' &&
638+
(method === 'item/commandExecution/requestApproval' ||
639+
method === 'item/fileChange/requestApproval')
640+
) {
641+
send(frame({ id, result: { decision: 'accept' } }));
642+
return [];
643+
}
631644
if (method === 'item/commandExecution/requestApproval') {
632645
const p = params as CommandExecutionRequestApprovalParams;
633646
const callId = `approval-${++approvalSeq}`;

0 commit comments

Comments
 (0)