Skip to content

fix(agent): stop a failed detached run from crashing the process - #4411

Merged
kwakayama merged 3 commits into
mainfrom
fix-detached-run-unhandled-rejection
Sep 4, 2026
Merged

kwakayama merged 3 commits into
mainfrom
fix-detached-run-unhandled-rejection

Conversation

@kwakayama

@kwakayama kwakayama commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

DetachedRunTracker.registerExecution's .finally() derives a new promise (trackedExecution) that's only ever read by waitForDrain, which only runs during shutdown. During normal operation nothing attaches a rejection handler to it, so a failed detached run became an unhandled promise rejection and crashed the whole process instead of just failing that one run.

How this was found

Staging's veryfront-agent pods were crash-looping — 6 restarts each in 16 minutes — once code#4407 let run-scoped inference credentials actually reach requireSecureInferenceApiBaseUrl in src/provider/veryfront-cloud/shared.ts for the first time (that check only runs when a run-scoped credential is present, which #4407 fixed the binding for). That check currently rejects staging's VERYFRONT_API_URL, which is a legitimate internal-cluster address (http://veryfront-api.veryfront-staging.svc.cluster.local) but plain HTTP, not HTTPS or loopback.

That URL-policy question is separate and intentionally not addressed by this PR. Whether a trusted internal cluster hostname should be treated as safe for run-scoped credentials is a real security tradeoff, not something to silently loosen as a side effect of a crash fix. This PR only stops the crash: the underlying request will still fail cleanly with a 400 (CONFIG_INVALID) until that policy question is resolved separately — it just won't take the process down anymore.

Verification

  • New regression test using a real unhandledrejection listener on globalThis, negative-controlled: reverted the fix and confirmed the test fails (unhandled captures the rejection) before restoring it.
  • Full detached-run-tracker.test.ts suite: 7/7 passing.
  • deno fmt / deno lint / deno check clean on both changed files.
  • Semantic-unit-boundary lint gate unaffected (no new global-mutation surface).

Summary by CodeRabbit

  • Bug Fixes
    • Prevented rejected background executions from triggering unhandled promise rejection errors or crashing the process.
    • Preserved existing shutdown behavior while ensuring execution failures remain safely handled.

registerExecution's .finally() derives a new promise that is only
ever read by waitForDrain, which runs on shutdown. During normal
operation nothing attaches a rejection handler to it, so a failed
detached run became an unhandled promise rejection and crashed the
process instead of just failing that one run.

Surfaced by staging's veryfront-agent pods crash-looping (6 restarts
each in 16 minutes) once #4407 let run-scoped inference credentials
actually reach requireSecureInferenceApiBaseUrl, which currently
rejects staging's plain-HTTP internal VERYFRONT_API_URL. That
rejection is a separate, pre-existing policy question; this change
only stops it (or any other detached-run failure) from taking the
whole process down.
Copilot AI lite review requested due to automatic review settings September 4, 2026 15:01

@greptile-apps greptile-apps 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.

kwakayama has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-09-04T15:37:08.183567Z 345c743 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

📦 Client bundle boundary

Entrypoint Modules Source size Server leaks
src/index.client.ts 288 2279 KiB ✅ 0

A server module in a client graph aborts hydration in the browser. New leaks fail CI; known leaks are tracked in scripts/lint/client-bundle-baseline.json to burn down.

@gitar-bot

gitar-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

Note

Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime.
Learn more

Code Review ✅ Approved

Stops a failed detached run from crashing the process by adding a rejection handler to the promise derived in DetachedRunTracker.registerExecution's .finally() block. Includes a regression test using unhandledrejection listener and passes all existing tests. No issues found.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Important

Your trial ends in 4 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

@coderabbitai

coderabbitai Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 24 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 9b9fd34d-685a-4338-903a-fb36527d56ab

📥 Commits

Reviewing files that changed from the base of the PR and between 995e6a2 and 345c743.

📒 Files selected for processing (2)
  • src/agent/service/detached-run-tracker.test.ts
  • src/agent/service/detached-run-tracker.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: aef19550-d484-44eb-8a28-88be120c2d7f

📥 Commits

Reviewing files that changed from the base of the PR and between 94350f0 and 995e6a2.

📒 Files selected for processing (2)
  • src/agent/service/detached-run-tracker.test.ts
  • src/agent/service/detached-run-tracker.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The detached run tracker now attaches a no-op rejection handler to registered execution promises. A test verifies that rejected executions do not emit unhandledrejection.

Changes

Detached run rejection handling

Layer / File(s) Summary
Suppress rejected execution events
src/agent/service/detached-run-tracker.ts, src/agent/service/detached-run-tracker.test.ts
registerExecution handles rejected execution promises. The test confirms that no unhandledrejection event is emitted.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 995e6

Failed detached runs will remain individual run failures rather than causing unhandled promise rejections that terminate the process. The targeted regression coverage supports merge readiness.

🚥 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: preventing failed detached runs from crashing the process.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files.
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-detached-run-unhandled-rejection

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.

Copy link
Copy Markdown
Contributor Author

Automated Code Review

Score: 93/100 — Excellent. Minimal, well-tested fix for a real production crash, with the fix and the unrelated policy question cleanly separated.

Strengths

  • Correct root cause and correct fix: execution.finally(...) in registerExecution produces a new promise (trackedExecution) that only waitForDrain ever reads; nothing else attaches a handler, so a failed detached run became an unhandled rejection that took the whole process down. Adding trackedExecution.catch(() => {}) suppresses that without altering waitForDrain's behavior — it still reads the same trackedExecution reference stored in activeExecutions, and waitForDrain uses Promise.allSettled, which never throws regardless.
  • Confirmed the original rejection is still observed and logged: the caller (durable-chat-run-start.ts) awaits detachedExecution directly and its onError handler logs the failure, so this change only removes the duplicate, unhandled rejection surface — it doesn't silently eat errors.
  • Good regression test: uses a real unhandledrejection listener on globalThis rather than mocking, and the PR description states it was negative-controlled (reverts the fix, confirms the test fails) — that's the right way to validate a fix for this class of bug.
  • Tight scope and honest PR description: staging's plain-HTTP VERYFRONT_API_URL triggering requireSecureInferenceApiBaseUrl is explicitly called out as a separate security/policy question and not addressed here, rather than quietly loosened as a side effect. That's the right call for a crash-fix PR.
  • Commit message and PR body clearly explain the "how this was found" chain (fix(agent): bind inference token on default chat dispatch #4407 → staging crash loop → this fix), which will help whoever reads this in git blame later.

Minor / optional suggestions (non-blocking)

  • Consider a one-line comment in the test itself (not just the PR description) noting it was negative-controlled by reverting the fix — that provenance is easy to lose once the PR description is no longer next to the code.
  • Not required for this fix, but worth a follow-up issue: since trackedExecution's rejection is now fully suppressed, if waitForDrain is ever refactored to stop being the sole reader, there'd be no signal at all from this promise — the current design is fine because the real error is already logged upstream, but that invariant is easy to break silently in a future change without a comment near activeExecutions.set(...) too.

This is a textbook example of a scoped incident-response fix — small diff, clear regression test, and no scope creep into the adjacent (and genuinely separate) TLS/loopback policy decision.


Generated by Claude Code

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.

🟢 Approval recommended

The change addresses the unhandled rejection root cause with minimal behavioral impact and includes a focused regression test.

Pull request overview

Fixes a production stability issue in the agent detached-run tracker where a failed detached execution could surface as an unhandled promise rejection and crash the process, even though the failure should be isolated to that run.

Changes:

  • Attach a rejection handler to the internally tracked execution promise to prevent unhandled rejections during normal operation.
  • Add a regression test that installs a real unhandledrejection listener and asserts no event fires for a failing registered execution.
File summaries
File Description
src/agent/service/detached-run-tracker.ts Prevent unhandled rejections by attaching a catch handler to the derived tracked promise.
src/agent/service/detached-run-tracker.test.ts Add regression coverage ensuring a failing tracked execution does not emit unhandledrejection.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/agent/service/detached-run-tracker.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@greptile-apps greptile-apps 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.

kwakayama has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

@codex review

@kwakayama
kwakayama enabled auto-merge September 4, 2026 15:09
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 87473f5c8b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

globalThis.addEventListener is a Deno/browser-only API; it does not
exist under Node, where the CI node-shard job runs this exact test.
Replace it with a small helper that uses the WHATWG event API when
available and falls back to process.on/process.off (bound to
process, since EventEmitter methods rely on their receiver)
otherwise. Verified directly under plain Node (v25.9.0) in both
directions: the fix observes no unhandled rejection, and a
deliberately un-fixed version does.

@greptile-apps greptile-apps 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.

kwakayama has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 345c743a60

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

@kwakayama
kwakayama added this pull request to the merge queue Sep 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 4, 2026
@kwakayama
kwakayama added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 2351f95 Sep 4, 2026
56 checks passed
@kwakayama
kwakayama deleted the fix-detached-run-unhandled-rejection branch September 4, 2026 16:21
@kwakayama kwakayama mentioned this pull request Sep 4, 2026
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.

3 participants