Skip to content

fix(host-service): classify terminal session failures by kind, not message text - #6432

Merged
saddlepaddle merged 2 commits into
mainfrom
sentry/hs-session-create-classify
Aug 13, 2026
Merged

saddlepaddle merged 2 commits into
mainfrom
sentry/hs-session-create-classify

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Aug 13, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Every terminal session failure reaching tRPC was classified by substring-matching the error's English message, and three call sites skipped even that and hard-coded a status.

Two Sentry issues on 1.20.2 are the visible symptom:

  • HOST-SERVICE-27 — launching an agent into a workspace whose worktree was already deleted. Routine lifecycle state, filed as a 500.
  • HOST-SERVICE-2B — session creation on a memory-pressured machine where the pty daemon did not become ready in its budget.

Two more misclassifications were found while fixing those, neither of which had a Sentry issue yet:

  • terminal.writeInput threw a blanket NOT_FOUND for every failure, so a session owned by a different workspace was reported to the caller as "missing" rather than FORBIDDEN.
  • terminalAgents.getOrCreate threw a blanket INTERNAL_SERVER_ERROR for every creation outcome, the same shape as the two above.

Root cause

packages/host-service/src/terminal/terminal.ts produces session errors as { error: string } with hand-written prose, at ~15 sites across five functions. The type information existed at the producer and was discarded, so the boundary reconstructed it by guessing:

if (message.includes("belong")) → FORBIDDEN
if (message.includes("not found") || message.includes("not active") || message.includes("exited")) → NOT_FOUND

Two of those producer sites pass an arbitrary upstream exception message straight through. Any such message containing "not found" would have been silently reclassified as a non-reporting NOT_FOUND, hiding a real bootstrap failure. That is the failure mode the no-allowlist rule from #6164 exists to prevent, relocated from the middleware into a helper.

The producer had also already begun growing structured discriminants for this exact reason — transient for daemon reachability, and code: "session-gone" on the websocket path — and the HTTP attach route already branches on transient to pick 503 vs 500. Only the tRPC boundary ignored the structure and matched prose.

Fix

Give the producer a discriminated union and let each consumer decide its own mapping.

  • TerminalSessionErrorKind / TerminalSessionError are exported from the terminal module. Every producer (writeInputToSession, getOrAdoptSession, writeFramedInputToSession, snapshotSession, createTerminalSessionInternal) returns one, and every existing site sets a kind.
  • The error string field is unchanged, so the HTTP attach route, the websocket path and existing tests keep working untouched. transient is retained for those consumers.
  • toTerminalIoError(message: string) becomes toTerminalSessionError(failure): one exhaustive switch, no string matching. All five tRPC call sites use it.
  • TERMINAL_START_FAILED is the only kind that stays INTERNAL_SERVER_ERROR, so it is the only one the Sentry middleware reports.

DAEMON_UNAVAILABLE maps to SERVICE_UNAVAILABLE, derived from the transient flag the producer already computed rather than from the message. HOST-SERVICE-2B's "daemon: timed out after 5000ms" is raised as a DaemonUnavailableError, i.e. the supervisor heals it and the session may still come up under the same id, so it stops paging. A permanent bootstrap failure (missing binary, no socket path, crash circuit open, handshake rejected) stays TERMINAL_START_FAILED and keeps reporting.

The switch is exhaustive by construction: adding a kind without deciding its code is a compile error at the mapper. Verified by temporarily adding a kind:

src/trpc/router/terminal/errors.ts(34,10): error TS2322: Type '"PROBE_NEW_KIND"' is not assignable to type 'never'.

This supersedes the first commit on this branch, which shared the old string classifier between the two creation call sites. That fixed the symptom but spread the mechanism.

Verification

bunx biome check on the five changed files:

Checked 5 files in 24ms. No fixes applied.

cd packages/host-service && bun run typecheck:

$ tsc --noEmit --emitDeclarationOnly false
typecheck exit=0

bun test src/trpc/router/:

 384 pass
 0 fail
Ran 384 tests across 29 files. [37.75s]

No test was added for the mapper: its exhaustiveness is enforced by the compiler, which is a stronger guarantee than a case-by-case test would give.

Refs HOST-SERVICE-27
Refs HOST-SERVICE-2B

Summary by CodeRabbit

  • Bug Fixes
    • Improved terminal session error handling for more accurate responses.
    • Permission-related failures now return an appropriate access error.
    • Deleted or unavailable worktrees are identified clearly.
    • Terminal lifecycle failures and unexpected I/O issues now use consistent error reporting.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fb210896-5d80-44b6-bb03-10b00977b493

📥 Commits

Reviewing files that changed from the base of the PR and between 81780b9 and fb993dc.

📒 Files selected for processing (5)
  • packages/host-service/src/terminal/terminal.ts
  • packages/host-service/src/trpc/router/agents/agents.ts
  • packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts
  • packages/host-service/src/trpc/router/terminal/errors.ts
  • packages/host-service/src/trpc/router/terminal/terminal.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/host-service/src/trpc/router/agents/agents.ts

📝 Walkthrough

Walkthrough

Terminal operations now return structured session errors. A shared mapper converts these errors to specific TRPC responses. Terminal, agent, and terminal-agent routers use the mapper for session creation and terminal operations.

Changes

Terminal error handling

Layer / File(s) Summary
Structured terminal session errors
packages/host-service/src/terminal/terminal.ts
Terminal input, snapshot, adoption, and creation flows classify missing sessions, workspace mismatches, inactive sessions, daemon outages, and startup failures.
Shared TRPC error conversion
packages/host-service/src/trpc/router/terminal/errors.ts, packages/host-service/src/trpc/router/terminal/terminal.ts
toTerminalSessionError maps structured failures to NOT_FOUND, FORBIDDEN, SERVICE_UNAVAILABLE, or INTERNAL_SERVER_ERROR responses.
Router integration
packages/host-service/src/trpc/router/agents/agents.ts, packages/host-service/src/trpc/router/terminal-agents/terminal-agents.ts, packages/host-service/src/trpc/router/terminal/terminal.ts
Terminal-related routers delegate session and terminal-operation failures to the shared mapper.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Mergeability Score: 🔵 Low · up to fb993

The change correctly narrows terminal session error classification while preserving reporting for daemon startup failures; merge is reasonable with owner awareness that the repository-wide lint check must still pass.

Possibly related PRs

Suggested reviewers: kitenite

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commit format and clearly summarizes the main change: classifying terminal session failures by kind.
Description check ✅ Passed The description explains the problem, root cause, fix, and verification results, although it omits the template checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentry/hs-session-create-classify

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

…ssage text

Replace the prose-matching classifier with a discriminated union on the
producer. Every session producer now returns a TerminalSessionError
carrying a `kind`, and the tRPC boundary switches on it exhaustively.

Fixes three misclassifications and removes the mechanism behind them:

- agents.run and terminal.createSession filed worktree-gone as a 500
- terminal.writeInput filed a wrong-workspace access as NOT_FOUND
  rather than FORBIDDEN
- terminalAgents.getOrCreate filed every creation outcome as a 500

The daemon-readiness timeout now maps to SERVICE_UNAVAILABLE via the
`transient` signal the producer already computed, so a healable daemon
blip stops paging while a permanent bootstrap failure still reports.
@saddlepaddle saddlepaddle changed the title fix(host-service): classify terminal session-creation failures instead of blanket 500s fix(host-service): classify terminal session failures by kind, not message text Aug 13, 2026
@saddlepaddle
saddlepaddle merged commit cebe58d into main Aug 13, 2026
25 of 26 checks passed
@saddlepaddle
saddlepaddle deleted the sentry/hs-session-create-classify branch August 22, 2026 22:26

This branch was previously deployed

1 inactive deployment
preview — fb993dce Deployed Aug 13, 2026 by saddlepaddle via Deploy Web #7661
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