fix(session-manager): writeOutboundDirect opens outbound.db read-only — command-gate denials never deliver#2738
Merged
Merged
Conversation
writeOutboundDirect opened the session's outbound DB through openOutboundDb, which sets readonly: true. The INSERT it then runs threw SQLITE_READONLY on every call, so the command-gate denial path (router.ts) never delivered its 'Permission denied' response — the sender just got silence, and the throw aborted routing for that inbound event. Switch to the openOutboundDbRw wrapper, which opens the same path with write access (DELETE journal + busy_timeout). The host-side write to the container-owned outbound.db is safe: both sides use DELETE journal mode, and the even host seq stays out of the container's odd-seq space. Adds a guard test that drives writeOutboundDirect against a real session folder and asserts the denial rows land in messages_out with even seqs; it goes red if the open call reverts to the readonly form. Co-Authored-By: Claude Fable 5 <[email protected]>
This was referenced Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type of Change
.claude/skills/<name>/, no source changes)Description
What:
writeOutboundDirect(src/session-manager.ts) opens the session's outbound DB viaopenOutboundDb, which setsreadonly: true. The INSERT it then runs throwsSQLITE_READONLYon every call.Why it matters: the only live caller is the command-gate denial path in
src/router.ts— when a non-admin sends an admin command, the host writes a "Permission denied" response straight intomessages_out. With the readonly open, that write always throws: the sender gets silence instead of a denial, and the throw escapesrouteInboundand aborts routing for the whole inbound event. Reproduced in production in #2495 / confirmed in #2496 (stack trace:SqliteError: attempt to write a readonly databaseatwriteOutboundDirect).How: one-line fix — open through the existing
openOutboundDbRwwrapper (same path, write access, DELETE journal + busy_timeout). The host-side write to the container-owned outbound.db is safe: both sides use DELETE journal mode, and the host's even seq numbers stay out of the container's odd-seq space. A short comment in the function doc records that rationale.This builds on #2496 (same one-line fix, sitting since May) by adding a regression test so the bug can't quietly come back. Closes #2495.
Tested:
src/session-manager.test.tsdrives the realwriteOutboundDirectagainst a real session folder: asserts denial rows land inmessages_outwith even host seqs, and thatINSERT OR IGNOREretry semantics hold. Reverting the fix (RW → readonly open) turns both cases red withSQLITE_READONLY.pnpm run build,pnpm run typecheckclean, lint at baseline (114 problems / 13 errors, all pre-existing). Container suite untouched by this diff: 100/101 pass; the one failure is the knownupload-tracetest that makes a live network call and times out, identical on the unmodified base.🤖 Generated with Claude Code