Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions packages/cli/src/nonInteractive/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function createConfig(overrides: ConfigOverrides = {}): Config {
getDebugMode: () => false,
getApprovalMode: () => 'auto',
getOutputFormat: () => 'stream-json',
getWarnings: () => [],
initialize: vi.fn(),
waitForMcpReady: vi.fn().mockResolvedValue(undefined),
getMonitorRegistry: () => mockMonitorRegistry,
Expand Down Expand Up @@ -330,6 +331,30 @@ describe('runNonInteractiveStreamJson', () => {
expect(mockDispatcher.dispatch).toHaveBeenCalledWith(initRequest);
});

it('writes only warnings produced during deferred initialization to stderr', async () => {
const warnings = ['Warning: already emitted before stream-json startup'];
const stderrWrite = vi.spyOn(process.stderr, 'write').mockReturnValue(true);
config = createConfig({
getWarnings: () => warnings,
initialize: vi.fn().mockImplementation(async () => {
warnings.push('Warning: emitted during stream-json initialization');
}),
});

mockInputReader.read = async function* () {
yield createControlRequest('initialize');
};

await runNonInteractiveStreamJson(config, '');

expect(stderrWrite).toHaveBeenCalledWith(
'Warning: emitted during stream-json initialization\n',
);
expect(stderrWrite).not.toHaveBeenCalledWith(
'Warning: already emitted before stream-json startup\n',
);
});

it('processes user message when received as first message', async () => {
const userMessage = createUserMessage('Hello world');

Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/nonInteractive/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ class Session {
debugLogger.debug('[Session] Initializing config');

try {
// gemini.tsx has already emitted warnings known before stream-json
// initialization starts. Keep that snapshot so only warnings produced
// by the deferred initialize() call are written here.
const emittedWarnings = new Set(this.config.getWarnings());
// Bracket `config.initialize()` with the same profiler checkpoints
// the non-stream-json branch in `gemini.tsx` uses so the
// `config_initialize_dur` derived phase shows up in stream-json
Expand All @@ -166,6 +170,11 @@ class Session {
profileCheckpoint('config_initialize_start');
await this.config.initialize(options);
profileCheckpoint('config_initialize_end');
for (const warning of this.config.getWarnings()) {
if (emittedWarnings.has(warning)) continue;
emittedWarnings.add(warning);
process.stderr.write(`${warning}\n`);
}
// Stream-json sessions feed prompts straight to the model after init.
// Under progressive MCP availability `initialize()` returns before
// MCP servers settle, so we must explicitly await discovery here —
Expand Down
Loading