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
29 changes: 29 additions & 0 deletions server/__tests__/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,35 @@ describe('discoverSessionWorktrees integration', () => {
});
});

describe('headless session does not pass resume on first query', () => {
let appSource: string;

beforeAll(async () => {
const { readFileSync } = await import('fs');
const { join } = await import('path');
appSource = readFileSync(join(import.meta.dirname, '..', 'app.ts'), 'utf-8');
});

it('headless startChat call omits resume option', () => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 style: Source-code-scanning tests (reading app.ts/chat.ts as strings and asserting on substrings) are fragile — any refactor that changes whitespace, variable names, or call structure will break them silently or produce false positives. The indexOf(');', startChatIdx) heuristic fails if the options object contains a nested ); sequence (e.g. in a comment or string). Consider testing the actual behavior (e.g. mock startChat and assert the options it receives) rather than parsing source text.

// Find the headless startChat block (identified by NullTransport + headless clientId)
const headlessMarker = 'const clientId = `headless:${wtId}`';
const headlessIdx = appSource.indexOf(headlessMarker);
expect(headlessIdx).toBeGreaterThan(-1);

// Extract the startChat call after the headless marker
const startChatIdx = appSource.indexOf('await startChat(', headlessIdx);
expect(startChatIdx).toBeGreaterThan(-1);

// Get the options object passed to startChat (up to the closing paren + semicolon)
const callEnd = appSource.indexOf(');', startChatIdx);
const callRegion = appSource.slice(startChatIdx, callEnd);

// Must NOT contain resume as an option key (resume: ...)
expect(callRegion).not.toMatch(/resume\s*[,:]/);
expect(callRegion).not.toMatch(/resume\s*\?/);
});
});

describe('validateResumable', () => {
it('returns valid for a CWD that passes git check', async () => {
const { validateResumable } = await import('../chat.js');
Expand Down
5 changes: 4 additions & 1 deletion server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,10 @@ async function handleSessionCreate(
const clientId = `headless:${wtId}`;
const transport = new NullTransport();
await startChat(transport, clientId, initialPrompt, {
resume: wtId,
// No resume — this is the first query, no SDK session exists yet.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 style: The 4-line comment explaining why resume is omitted is helpful for the initial commit but may become noise long-term. A single line like // First query — no SDK session to resume yet would suffice given that the commit message and the updateSessionSdkId call site already document the lifecycle. [fixable]

// The SDK session UUID is captured in query-loop after the first
// assistant message and stored via updateSessionSdkId() for future
// queries (e.g. user replies from their phone).
mode: mode ?? 'agent',
model,
isolation: true,
Expand Down
Loading