Skip to content

V7: Return 409 with Retry-After when a channel's session is busy - #675

Merged
LinuxDevil merged 2 commits into
mainfrom
v7-channel-session-busy
Oct 19, 2026
Merged

LinuxDevil merged 2 commits into
mainfrom
v7-channel-session-busy

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

Fixes audit finding SWE-2 (Vercel competitor audit, 2026-10-19).

What was wrong

Two channel replicas (or two agents) on one SQLite store, same sessionKey, 0.7 s apart: the second replica's turn hit the run lease and answered HTTP 500 {"error":"The request failed…","code":"LOUSHO_SESSION_BUSY"}, logged as [webhook] parse failed. A turn that failed before the ack was thrown to the host, which reported every such error with stage 'parse' and mapped it to 500; only approval conflicts were 409.

What changed

  • channelCore: a turn that fails before the channel acknowledged the request is now answered and reported by the core. LOUSHO_SESSION_BUSY is a 409 { error, code: "LOUSHO_SESSION_BUSY" } with Retry-After: 2; any other error is still a 500 with only the error's code. Both go to onError with stage 'turn' (and the session id), not 'parse'.
  • New exported ChannelCoreRespond type: (status, body, headers?). ChannelRespond for channels is unchanged, and a 2-arg respond still type-checks.
  • mountChannels / mountFetchChannels: forward the optional headers onto the response. These are the hosts that own the transport, so channelCore can't set headers by itself. This is the minimal plumbing outside channelCore.
  • Platform channels (Slack, Discord, Telegram, Teams, GitHub) acknowledge in parse, so a busy turn is already post-ack for them: they keep their 200 and the in-conversation "request failed" reply. No change there.
  • docs/channels.md: documents the 409 + Retry-After and the 'turn' stage.

Tests

  • New src/channels/channelCore.busy.test.ts: a stubbed agent whose turn throws SDKError(LOUSHO_SESSION_BUSY) through httpChannel() gets 409 + numeric Retry-After + code: LOUSHO_SESSION_BUSY on both mountChannels and mountFetchChannels, and onError is called with stage: 'turn'. A generic pre-ack turn failure is still a 500 that hides the message, now reported as stage 'turn'. All 3 failed before the fix (500 / stage 'parse').
  • npx vitest run src/channels src/deploy src/server: 50 files, 618 tests passed.
  • npx tsc --noEmit, npm run typecheck:tests, npm run lint: clean.
  • npm run test:coverage: only the known baseline failures remain (scripts/changelogGuard.test.ts, create-lousho-agent index.test.ts/template.e2e.test.ts). One timing flake in src/tools/mcp/connect.test.ts under full-suite load passes alone (35/35). npm run fallow: exit 0.

Changelog: Channels answer a turn on a session another process or replica is running (LOUSHO_SESSION_BUSY) with 409 + Retry-After instead of a 500, and report pre-ack turn failures to onError with stage 'turn' instead of 'parse'.

🤖 Generated with Claude Code

LinuxDevil and others added 2 commits October 19, 2026 23:38
…t it as a failed turn

A channel turn on a session another process or replica runs (its run lease
held: LOUSHO_SESSION_BUSY) was a 500 reported to onError with stage 'parse'.
channelCore now answers a turn that fails before the ack itself: a 409 with
Retry-After and the coded error for a busy session, else the usual 500; both
are reported with stage 'turn'. The hosts forward response headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LinuxDevil
LinuxDevil force-pushed the v7-channel-session-busy branch from 9f02402 to 7beff3c Compare October 19, 2026 20:41
@LinuxDevil
LinuxDevil merged commit 7cf8fb2 into main Oct 19, 2026
2 checks 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