Skip to content

fix(server): skip --resume on first headless query#355

Merged
dimakis merged 2 commits into
mainfrom
fix/headless-session-resume
May 22, 2026
Merged

fix(server): skip --resume on first headless query#355
dimakis merged 2 commits into
mainfrom
fix/headless-session-resume

Conversation

@dimakis
Copy link
Copy Markdown
Owner

@dimakis dimakis commented May 22, 2026

Summary

  • Headless sessions (POST /api/sessions with initialPrompt) passed the Mitzo worktree ID as --resume to the Claude Code SDK, which rejects non-UUID values — causing 100% failure on all headless dispatch (PR Shepherd, etc.)
  • Fix: omit resume on the first headless query since no SDK session exists yet. The UUID is captured via updateSessionSdkId() after the first assistant message and used for subsequent queries.
  • Design doc: local_features/headless-session-resume/design.md (in mgmt)

Test plan

  • Structural test: headless startChat() call omits resume option
  • Structural test: interactive resume path still resolves SDK session ID via getSessionSdkId()
  • Manual: POST /api/sessions with initialPrompt → first query succeeds
  • Manual: PR Shepherd dispatch creates sessions that run the skill
  • Manual: user reply from phone resumes the SDK session correctly

🤖 Generated with Claude Code

Headless sessions (POST /api/sessions with initialPrompt) passed the
Mitzo worktree ID as --resume to the Claude Code SDK, which rejects
non-UUID values. The first query has no SDK session to resume — omit
resume and let the SDK create a new session. The UUID is captured via
updateSessionSdkId() after the first assistant message for subsequent
queries.

Fixes 100% failure rate on PR Shepherd and all headless session dispatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@dimakis dimakis left a comment

Choose a reason for hiding this comment

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

Centaur Review

Found 2 issue(s) (1 warning).

server/__tests__/chat.test.ts

Correct one-line fix (removing resume: wtId for first headless query). The logic is sound and well-commented, but the tests rely on fragile source-code string scanning rather than behavioral assertions.

  • 🟡 style (L473): 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.
  • 🔵 missing_tests (L492): The second test ('interactive resume path still resolves SDK session ID') duplicates the existing test at line ~346 which already asserts chatSource.toContain('getSessionSdkId(BASE_REPO, options.resume)'). This is redundant coverage — both read chat.ts source and check the same substring. [fixable]

chatSource = readFileSync(join(import.meta.dirname, '..', 'chat.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.

Comment thread server/__tests__/chat.test.ts Outdated
expect(callRegion).not.toMatch(/resume\s*\?/);
});

it('interactive resume path still resolves SDK session ID', () => {
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.

🔵 missing_tests: The second test ('interactive resume path still resolves SDK session ID') duplicates the existing test at line ~346 which already asserts chatSource.toContain('getSessionSdkId(BASE_REPO, options.resume)'). This is redundant coverage — both read chat.ts source and check the same substring. [fixable]

The interactive-resume-path test duplicated existing coverage at line
~346. Removed per Centaur review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@dimakis dimakis left a comment

Choose a reason for hiding this comment

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

Centaur Review

Found 1 issue(s).

server/app.ts

Correct fix — the headless path was passing a worktree ID as a resume token before any SDK session existed, which would cause the SDK to reject the query. The test follows an established source-scanning pattern in this repo. LGTM with a minor comment verbosity nit.

  • 🔵 style (L483): 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]

Comment thread server/app.ts
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]

@dimakis dimakis merged commit adacbbc into main May 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant