Skip to content

fix(server): report agent stream 5xx failures to Sentry - #3363

Merged
kwakayama merged 2 commits into
mainfrom
fix/agent-stream-5xx-sentry
Aug 4, 2026
Merged

kwakayama merged 2 commits into
mainfrom
fix/agent-stream-5xx-sentry

Conversation

@kwakayama

@kwakayama kwakayama commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

The problem

Request handlers catch every error and convert it to an HTTP response. Sentry only sees what escapes to a global handler — uncaught exceptions and unhandled rejections. A caught-and-converted error never escapes, so every per-request failure is invisible in Sentry by construction.

Confirming evidence: every issue currently in the veryfront-server Sentry project is a startup/bootstrap failure (VERYFRONT_TRUST_FORWARDED_HEADERS, AddrInUse, config-load errors). Those throw outside a request handler. Nothing per-request is there.

This is what made the isolated-runtime incident expensive: agent chat on staging returned 500 on every attempt for hours, with nothing in Sentry, nothing useful in logs, and no detail in the response. It was found only because a user reported broken chat.

The design flaw

The handled/unhandled boundary was drawn at "is it a VeryfrontError" rather than "is it 5xx".

That's right for the common case — most VeryfrontErrors are 4xx (validation, auth) and reporting them would flood Sentry. But it swallows server errors too, which are exactly the ones worth paging on.

The boundary should be status, not error type. 5xx reports; 4xx doesn't.

The change

Both 5xx exits of the agent-stream catch block now report:

Exit Boundary Why
isVeryfrontError + status >= 500 agent.stream.request Sits alongside the logging added in #3359
generic fallback agent.stream.handler An unexpected TypeError produces a bare one-line log with no slug, detail, or stack — the case where a captured stack is worth the most

Each event carries slug, category, detail, project.id, project.slug, http.status and the run id as requestId, so an event is actionable without a Loki dive.

Notes for review

detail is in the event but still not in the response. errorToResponse strips detail from 5xx bodies at src/errors/http-error.ts:104-106. That is unchanged, and the existing test still asserts the caller never receives it. Forwarding it to Sentry is the whole point — it is the field that identified the isolated-runtime root cause.

PII. All 7 detail values this handler constructs are static string literals — no interpolated tokens, paths, or user input. Errors from deeper code are the residual risk, so the guarantee rests on the sanitizer rather than that audit: sanitizeTelemetryAttributes strips URL credentials, redacts sensitive key names, and truncates every value. This is the same treatment detail already gets in Loki today.

Sentry is optional. captureApplicationError is a no-op when no reporter is installed and swallows its own failures internally — so framework users running veryfront without Sentry configured are unaffected, and a reporter fault can never replace the application failure that triggered it. No call-site guard needed.

No double-reporting. The two branches are mutually exclusive — the typed branch returns, so the generic path cannot also fire for the same error. Nothing inside the try captures-and-rethrows. This is asserted behaviorally (captures.length === 1) rather than guarded with a WeakSet, so a future rethrow turns the test red instead of being silently absorbed. If a second capture site ever appears here, dedupe belongs in captureApplicationError itself.

Tests

Three cases in agent-stream.handler.test.ts, modelled on the existing 5xx logging test — injecting a throw via ensureProjectDiscovery and installing a stub via setApplicationErrorReporter:

  1. 5xx VeryfrontError reports exactly once, with full context
  2. unexpected non-VeryfrontError reports exactly once
  3. 4xx stays silent

Red proven by flipping the condition to >= 600: the typed 5xx test fails, the untyped one correctly still passes (different branch). Restored, all 45 steps in the file pass.

Gates: deno check --no-lock, deno lint, deno fmt --check, deno task lint:test-typecheck (baseline holds, 0 new). The test file was also typechecked directly to confirm it is not grandfathered.

Scope

Deliberately one path. A survey found agent-stream.handler.ts is the only file under src/server/handlers/ using isVeryfrontError/errorToResponse. Only three other request handlers convert a caught error to a 500 at all: channel-dispatch-request.ts, agent-run-resume.handler.ts, agent-run-cancel.handler.ts.

Those are follow-ups once events are confirmed landing in Sentry with useful context — not a blanket instrumentation pass in this PR.

Summary by CodeRabbit

  • Bug Fixes
    • Improved monitoring for agent stream failures.
    • Added contextual details to server-side error reports for faster diagnosis.
    • Prevented client-side (4xx) errors from being reported as application failures.
    • Added reporting for unexpected failures with appropriate internal-server error status.

Request handlers catch every error and convert it to an HTTP response, so
nothing escapes to a global handler. Sentry only sees uncaught exceptions
and unhandled rejections, which means every per-request failure has been
invisible by construction. Confirming evidence: every issue in the
veryfront-server Sentry project today is a startup/bootstrap failure.

The handled/unhandled boundary was drawn at "is it a VeryfrontError"
rather than "is it 5xx". That is right for the common case, since most
VeryfrontErrors are 4xx validation and auth outcomes that would flood
Sentry, but it swallows server errors too.

Draw the boundary at status instead. Both 5xx exits of the agent stream
catch block now report:

- the typed branch, alongside the logging added in #3359
- the generic fallback, where an unexpected TypeError produces a bare
  message with no slug, detail, or stack in the log

Each event carries slug, category, detail, project id, project slug and
the run id, so it is actionable without a Loki dive. `detail` is included
because errorToResponse deliberately strips it from 5xx bodies; that
stripping is unchanged and asserted by the existing test. Attributes go
through the reporter's sanitizer, which strips URL credentials, redacts
sensitive keys and truncates values.

Reporting is optional. captureApplicationError is a no-op when no
reporter is installed, which is the normal case for framework users
running veryfront without Sentry configured.

Scope is deliberately one path. A survey found agent-stream.handler.ts is
the only file under src/server/handlers/ using isVeryfrontError or
errorToResponse; only channel-dispatch-request.ts,
agent-run-resume.handler.ts and agent-run-cancel.handler.ts convert a
caught error to a 500 at all. Those are follow-ups once events are
confirmed landing in Sentry.
Copilot AI review requested due to automatic review settings August 4, 2026 16:41
@kwakayama
kwakayama requested a review from kojiwakayama as a code owner August 4, 2026 16:41
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 15a84319-3579-4c40-8b2b-4657133d011c

📥 Commits

Reviewing files that changed from the base of the PR and between ad051a5 and 1b0a8b2.

📒 Files selected for processing (2)
  • src/server/handlers/request/agent-stream.handler.test.ts
  • src/server/handlers/request/agent-stream.handler.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/server/handlers/request/agent-stream.handler.ts
  • src/server/handlers/request/agent-stream.handler.test.ts

📝 Walkthrough

Walkthrough

The agent stream handler now reports structured application errors for 5xx failures. Tests verify reporting context for typed 503 and unexpected 500 failures, and suppression for typed 400 failures.

Changes

Agent stream observability

Layer / File(s) Summary
Agent stream failure reporting
src/server/handlers/request/agent-stream.handler.ts
The handler builds sanitized error context and reports typed 5xx and unexpected failures with request, project, boundary, status, and run metadata.
Application-error reporting validation
src/server/handlers/request/agent-stream.handler.test.ts
Tests verify one report for typed 503 errors, reporting for unexpected 500 failures, and no report for typed 400 errors.

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

Sequence Diagram(s)

sequenceDiagram
  participant AgentStreamHandler
  participant ApplicationErrorReporter
  participant HTTPResponse
  AgentStreamHandler->>ApplicationErrorReporter: report structured 5xx failure context
  ApplicationErrorReporter-->>AgentStreamHandler: optional capture result
  AgentStreamHandler->>HTTPResponse: return existing error response
Loading

Possibly related PRs

Suggested reviewers: kojiwakayama, copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reporting agent stream 5xx failures to Sentry.
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 fix/agent-stream-5xx-sentry

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server/handlers/request/agent-stream.handler.ts`:
- Around line 742-744: Update the error-reporting path around
reportAgentStreamFailure and its getPathRunId call to catch malformed URL
parsing failures. When parsing the run ID throws, continue reporting the failure
without setting requestId, while preserving the existing run ID behavior for
valid paths and the generic 500 response.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b9a76af9-f719-4a79-a1d6-7d307fb172a3

📥 Commits

Reviewing files that changed from the base of the PR and between b76ea50 and ad051a5.

📒 Files selected for processing (2)
  • src/server/handlers/request/agent-stream.handler.test.ts
  • src/server/handlers/request/agent-stream.handler.ts

Comment thread src/server/handlers/request/agent-stream.handler.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR ensures server-side failures in the agent stream request handler are visible to the optional application-error reporter (for example, Sentry) even when the handler catches and converts errors into HTTP responses.

Changes:

  • Add reportAgentStreamFailure() to capture 5xx agent stream failures via captureApplicationError() with run-id correlation and sanitized attributes.
  • Report both typed 5xx VeryfrontError failures and unexpected non-VeryfrontError failures under distinct boundaries.
  • Add tests covering: typed 5xx reporting, untyped 500 reporting, and silence for typed 4xx errors.

Verification

  • Not run in this review environment.
  • Suggested next step: run the repo’s narrow/unit test command for this area (at minimum the agent-stream handler test file), then broaden if needed.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/server/handlers/request/agent-stream.handler.ts Adds best-effort application-error reporting for agent stream 5xx failures with correlation attributes (including run id as requestId).
src/server/handlers/request/agent-stream.handler.test.ts Adds tests to ensure 5xx is reported once with expected context, unexpected errors are reported once, and 4xx remains unreported.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +742 to +745
// `pathRunId` is scoped to the try block, so the run id is re-derived from
// the URL here. It is the identifier that ties an event to a single run.
const runId = getPathRunId(new URL(req.url).pathname);
if (details.slug) attributes["error.slug"] = details.slug;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, and fixed in 1b0a8b2 — you and CodeRabbit found this independently.

Confirmed the mechanism rather than assuming it: /api/control-plane/runs/run_%ZZ/stream matches RUN_STREAM_PATH_REGEX, and decodeURIComponent("run_%ZZ") throws URIError: URI malformed. The first decode throws inside the try and lands in the generic catch; the reporter then decoded a second time and threw past the catch, replacing the 500 with an uncaught failure.

Your framing is the right one and is now the invariant the code follows: reporting is strictly best-effort and must never throw. The decode is guarded and the event is sent without requestId when it fails.

Locked with a regression test — reverting only the guard reproduces the escaping URIError, so it fails for the right reason.

getPathRunId calls decodeURIComponent, which throws URIError on a malformed
percent escape such as /api/control-plane/runs/run_%ZZ/stream. That path
still matches the handler route, so the decode throws inside the try, lands
in the generic catch, and the reporter then decoded it a second time — this
time throwing straight past the catch and destroying the 500 response it was
meant to describe.

Reporting is diagnostic and must never replace the failure that triggered it.
Catch the decode and report without a requestId instead.

Replaces the plain-TypeError generic-branch test, which this case subsumes:
it reaches the same branch while also covering the throw. Also trims the
commentary on the reporting helper to what is not derivable from the code.
Copilot AI review requested due to automatic review settings August 4, 2026 16:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/server/handlers/request/agent-stream.handler.ts:1047

  • The fallback error response hard-codes status 500 while the surrounding code already uses HTTP_INTERNAL_SERVER_ERROR for reporting. Using the constant here avoids a magic number and keeps the reported status and response status coupled if the constant ever changes.
      reportAgentStreamFailure(error, req, ctx, {
        boundary: "agent.stream.handler",
        status: HTTP_INTERNAL_SERVER_ERROR,
      });
      return this.respond(builder.json({ error: "Internal agent stream failed" }, 500));

@kwakayama
kwakayama added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit c24ba8e Aug 4, 2026
32 checks passed
@kwakayama
kwakayama deleted the fix/agent-stream-5xx-sentry branch August 4, 2026 18:33
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.

2 participants