Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
3a02f0e
feat(core): add CodeModeOnly tool execution
DragonnZhang Aug 31, 2026
3b6c2be
fix(core): preserve CodeModeOnly execution semantics
DragonnZhang Aug 31, 2026
510a75f
Merge remote-tracking branch 'origin/main' into dragon/code-mode-only…
DragonnZhang Aug 31, 2026
66b99b8
fix(core): drop the tool_search reminder in CodeModeOnly
DragonnZhang Aug 31, 2026
9564f59
chore: refresh settings schema
DragonnZhang Aug 31, 2026
96ce4e5
chore: refresh companion notices
DragonnZhang Aug 31, 2026
225e42f
fix: register exec tool metadata
DragonnZhang Aug 31, 2026
036c68f
fix(cli): localize exec tool display name
DragonnZhang Aug 31, 2026
8b44607
fix(core): stop offering ToolSearch lookups in CodeModeOnly
DragonnZhang Aug 31, 2026
d70f883
feat(core): teach the system prompt CodeModeOnly's tool surface
DragonnZhang Aug 31, 2026
5110fa2
test: include code mode host in standalone fixture
DragonnZhang Aug 31, 2026
6c7c83e
Merge remote-tracking branch 'origin/main' into dragon/code-mode-only…
DragonnZhang Aug 31, 2026
8129280
fix(core): return code mode media as multimodal output
DragonnZhang Sep 1, 2026
421d645
feat(core): support image tool results in code mode
DragonnZhang Sep 1, 2026
625af8b
fix(core): carry deferred tool schemas in the exec description
DragonnZhang Sep 1, 2026
f6fb7b2
fix(node-repl): restore the bin exec bit after rebuild
DragonnZhang Sep 1, 2026
267d236
fix(cli): pin patched Ink version
DragonnZhang Sep 1, 2026
866696b
feat(cli): add collapsible tool call details
DragonnZhang Sep 1, 2026
6d38fed
fix(dev): use CLI tsconfig outside repository
DragonnZhang Sep 1, 2026
7200b9f
fix(cli): preserve expanded tool details after completion
DragonnZhang Sep 1, 2026
8b8ebe9
fix(cli): fail headless runs on model API errors
DragonnZhang Sep 1, 2026
cdd99f4
fix(core): expose shell timeout bounds in code mode
DragonnZhang Sep 1, 2026
a06f05c
feat(core): add code mode timeout helpers
DragonnZhang Sep 1, 2026
33e3c82
Merge remote-tracking branch 'origin/main' into HEAD
DragonnZhang Sep 2, 2026
f5200f5
feat(cli): expose code mode in settings
DragonnZhang Sep 2, 2026
7c94f02
chore: merge main into code mode branch
DragonnZhang Sep 5, 2026
2c4babc
chore: merge latest main into code mode branch
DragonnZhang Sep 5, 2026
69c6cca
chore: merge current main into code mode branch
DragonnZhang Sep 5, 2026
f5d6e85
chore: merge latest main into code mode branch
DragonnZhang Sep 5, 2026
8a80b5c
chore: merge latest main into code mode branch
DragonnZhang Sep 5, 2026
70c1d2f
Merge main into dragon/code-mode-only-10377
DragonnZhang Sep 5, 2026
f7788d1
Merge latest main into dragon/code-mode-only-10377
DragonnZhang Sep 5, 2026
fa2b864
fix(mcp): preserve structured results for model and exec consumers
DragonnZhang Sep 6, 2026
ef036b8
Merge remote-tracking branch 'origin/dragon/code-mode-only-10377' int…
DragonnZhang Sep 7, 2026
e79b362
Merge remote-tracking branch 'origin/main' into dragon/code-mode-only…
DragonnZhang Sep 7, 2026
2c0c343
fix(core): preserve code mode output and truncate inline
DragonnZhang Sep 7, 2026
9739b6e
feat(core): port scoped context tools to code mode
DragonnZhang Sep 7, 2026
18ddd6c
Merge origin/main into dragon/code-mode-only-10377
DragonnZhang Sep 8, 2026
71c6c07
fix(build): synchronize pnpm lockfile and capability docs
DragonnZhang Sep 9, 2026
a16cc55
Merge origin/main into dragon/code-mode-only-10377
DragonnZhang Sep 9, 2026
2d78cab
Merge origin/main into dragon/code-mode-only-10377
DragonnZhang Sep 9, 2026
f928f85
Merge origin/main into dragon/code-mode-only-10377
DragonnZhang Sep 9, 2026
973eaea
merge: sync code mode with main Responses API support
DragonnZhang Sep 10, 2026
3a07794
Merge remote-tracking branch 'origin/dragon/code-mode-only-10377' int…
DragonnZhang Sep 10, 2026
54b3668
Merge main into dragon/code-mode-only-10377
DragonnZhang Sep 10, 2026
2325365
fix(core): recover from rejected Responses encrypted reasoning
DragonnZhang Sep 10, 2026
d87366a
merge main to fix code mode CI
DragonnZhang Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(cli): fail headless runs on model API errors
  • Loading branch information
DragonnZhang committed Sep 1, 2026
commit 8b8ebe9047e83cec58f6b9b2f0ce7770f6a7a704
41 changes: 41 additions & 0 deletions packages/cli/src/nonInteractiveCli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4894,6 +4894,47 @@ describe('runNonInteractive', () => {
expect(errorOutput).toContain('Incorrect API key provided');
});

it('fails stream-json runs when the model stream emits an API error', async () => {
(mockConfig.getOutputFormat as Mock).mockReturnValue(
OutputFormat.STREAM_JSON,
);
setupMetricsMock();
const apiErrorEvent: ServerLlmStreamEvent = {
type: LlmEventType.Error,
value: {
error: {
message: '429 Too Many Requests',
status: 429,
},
},
};
mockLlmClient.sendMessageStream.mockReturnValue(
createStreamFromEvents([apiErrorEvent]),
);

await expect(
runNonInteractive(
mockConfig,
mockSettings,
'Test input',
'prompt-id-stream-api-error',
),
).rejects.toBeInstanceOf(AlreadyReportedError);

const messages = processStdoutSpy.mock.calls
.map((call) => String(call[0]).trim())
.filter(Boolean)
.map((line) => JSON.parse(line) as { [key: string]: unknown });
const results = messages.filter((message) => message['type'] === 'result');

expect(results).toHaveLength(1);
expect(results[0]).toMatchObject({
subtype: 'error_during_execution',
is_error: true,
error: { message: expect.stringContaining('429 Too Many Requests') },
});
});

it('does not double-wrap or double-format an API error in non-interactive mode', async () => {
// Regression test for the bug where a 4xx error event flowed through
// both the stream handler and handleError, each calling
Expand Down
29 changes: 12 additions & 17 deletions packages/cli/src/nonInteractiveCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2418,19 +2418,17 @@ export async function runNonInteractive(
}
loopDetected = true;
}
if (
outputFormat === OutputFormat.TEXT &&
event.type === LlmEventType.Error
) {
if (event.type === LlmEventType.Error) {
const errorText = parseAndFormatApiError(
event.value.error,
config.getContentGeneratorConfig()?.authType,
);
process.stderr.write(`${errorText}\n`);
// We have already formatted and written the message; mark the
// throw so the top-level handleError doesn't reformat (which
// would yield "[API Error: [API Error: ...]]") or print it a
// second time. Exit code stays 1 — same as before.
if (outputFormat === OutputFormat.TEXT) {
process.stderr.write(`${errorText}\n`);
}
// The adapter has already captured the formatted error in JSON
// modes, while text mode wrote it above. Mark the throw so the
// terminal error result is emitted without formatting it again.
throw new AlreadyReportedError(errorText);
}
}
Expand Down Expand Up @@ -2734,18 +2732,15 @@ export async function runNonInteractive(
}
loopDetected = true;
}
if (
outputFormat === OutputFormat.TEXT &&
event.type === LlmEventType.Error
) {
if (event.type === LlmEventType.Error) {
const errorText = parseAndFormatApiError(
event.value.error,
config.getContentGeneratorConfig()?.authType,
);
process.stderr.write(`${errorText}\n`);
// See the matching note in the first stream loop above —
// we mark the throw so handleError doesn't reformat or
// reprint downstream.
if (outputFormat === OutputFormat.TEXT) {
process.stderr.write(`${errorText}\n`);
}
// See the matching main-stream branch above.
throw new AlreadyReportedError(errorText);
}
}
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import { writeStderrLine } from './stdioHelpers.js';
const debugLogger = createDebugLogger('CLI_ERRORS');

/**
* Marker thrown when a producer has already formatted the error message and
* written it to stderr — the downstream `handleError` should propagate the
* exit code without printing or reformatting again.
* Marker thrown when a producer has already formatted and reported an error
* through the active output path — the downstream `handleError` should
* propagate the exit code without printing or reformatting it again.
*
* The non-interactive runner uses this when an upstream API error event
* arrives mid-stream: it formats with parseAndFormatApiError, writes once,
* arrives mid-stream: it formats with parseAndFormatApiError, reports it,
* and then throws. Without this marker, handleError would call
* parseAndFormatApiError a second time on the (now formatted) Error.message,
* yielding "[API Error: [API Error: ...]]" plus a duplicate stderr line.
Expand Down Expand Up @@ -145,7 +145,7 @@ export async function handleError(
config: Config,
customErrorCode?: string | number,
): Promise<never> {
// Producers that already wrote a formatted message to stderr (see
// Producers that already reported a formatted message (see
// AlreadyReportedError above) should not be reprinted or reformatted here.
// In TEXT mode this short-circuits straight to a clean re-throw; in JSON
// mode we still emit the structured payload exactly once so machine
Expand Down