Skip to content

fix: prevent infinite background agent resubscribe loop and add output guard#130

Open
harrryyd wants to merge 9 commits into
mainfrom
feat/review-mode-diff-panel-local-toggle
Open

fix: prevent infinite background agent resubscribe loop and add output guard#130
harrryyd wants to merge 9 commits into
mainfrom
feat/review-mode-diff-panel-local-toggle

Conversation

@harrryyd

Copy link
Copy Markdown
Owner

Root cause

wsTransport.subscribe() has an infinite for (;;) loop that auto-resubscribes after stream completion. runBackgroundAgent was wired through subscribe() (meant for persistent streams), causing the agent to be re-invoked infinitely — each completion spawned a new opencode process that made new changes, pushed, triggering another run.

The subscribe() re-trigger was verified via wsTransport.test.ts:479 which explicitly tests that a stream re-subscribes after normal exit.

Fixes

Client (useReviewComments.ts)

  • Call unsub() in done/error listeners to break the subscribe() loop after one completion
  • Batch agent path: track completed comments via Set, call unsub() when all done

Server (BackgroundAgentService.ts)

  • MAX_AGENT_OUTPUT_BYTES (500KB): output guard that kills runaway agent processes with SIGTERM
  • Improved prompt: instructs agent to STOP on vague/non-actionable comments rather than unconditionally forcing 'fix' mode

Tests (BackgroundAgentService.test.ts)

  • kills agent process when output exceeds MAX_AGENT_OUTPUT_BYTES — simulates flooding stdout, verifies child killed
  • prompt instructs agent to check if comment is actionable before making changes — verifies guard clauses in prompt

Test results

  • Full server suite: 1260 tests pass
  • Full web suite: 1131 tests pass (8 pre-existing failures in DiffCommentPanel.test.tsx unrelated to this change)

harrryyd added 9 commits June 13, 2026 11:31
…umber, render PR diff with @pierre/diffs

- Replace thread runtimeMode-based review mode with a local 'Review' toggle button in the diff panel header
- Auto-detect PR number from VCS status (same source as sidebar badge), fallback to overrideable input
- Render PR diff using Virtualizer+FileDiff from @pierre/diffs instead of raw <pre> text block
- Auto-switch diffSourceMode between 'checkpoint' and 'pr' when toggling review mode
…lose stdin

- Fix 'Agent process failed or timed out: undefined' by checking cause.message is truthy before use
- Add -m opencode/deepseek-v4-flash-free to opencode CLI args for fast background agent responses
- Close stdin with stdio: ['ignore', 'pipe', 'pipe'] to prevent agent process blocking
- Reduce AGENT_TIMEOUT_MS from 5min to 2min for faster failure feedback
- Add isDiffOpenValue handling for TanStack Router JSON-encoded search params
- Add E2E test (pr-review-agent-e2e.ts) exercising full background agent flow via WS RPC
…t guard

Root cause: wsTransport.subscribe() has an infinite for(;;) loop that
auto-resubscribes after stream completion. runBackgroundAgent was wired
through subscribe() (meant for persistent streams), causing the agent
to be re-invoked infinitely — each completion spawned a new opencode
process that made new changes, pushed, triggered another run.

Fix:
- Client (useReviewComments): call unsub() in done/error listeners to
  break the subscribe() loop after one completion
- Server (BackgroundAgentService): add MAX_AGENT_OUTPUT_BYTES (500KB)
  output guard that kills runaway agent processes
- Server (BackgroundAgentService): improve prompt to instruct agent to
  STOP on vague/non-actionable comments rather than forcing 'fix' mode

The subscribe() re-trigger was verified via wsTransport.test.ts at line
479 which explicitly tests that a stream re-subscribes after normal exit.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Jun 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7deddaea61

ℹ️ 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".

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const apiPath = `repos/${nameWithOwner}/pulls/${input.reference}/reviews`;
yield* execute({
cwd: input.cwd,
args: ["api", apiPath, "--input", input.commentsJsonFile],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use POST for inline review API calls

When a draft contains any line comment, this runs gh api repos/.../reviews --input ... without --method POST. The GitHub CLI manual says gh api defaults to GET unless field parameters are added, while --input only supplies the request body (https://cli.github.com/manual/gh_api), so these inline-review submissions hit the create-review endpoint with GET and fail instead of posting the review. Add --method POST (or -X POST) here.

Useful? React with 👍 / 👎.

: cli.createPullRequestReview({
cwd: input.cwd,
reference: String(input.prNumber),
bodyFile: commentsJsonFile,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass markdown to gh for file-only reviews

When the draft has only file-level comments (apiComments.length === 0), this passes the JSON payload file to gh pr review --body-file; the CLI manual says --body-file reads body text from the file (https://cli.github.com/manual/gh_pr_review), so GitHub will receive the raw {"event":"COMMENT",...} JSON as the review body instead of the formatted markdown in body. Use a markdown body file on this fallback path, or submit the JSON payload via gh api as well.

Useful? React with 👍 / 👎.

Comment on lines 341 to +342
Effect.runFork(Ref.update(combinedOutputRef, (prev) => prev + text));
safeOffer({
type: "detail",
commentId: input.commentId,
title: "Agent stderr",
content: text,
});
if (!killedByOutputGuard) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply the output guard to stderr

The new 500 KB guard only runs in the stdout handler; if an agent gets into a loop that logs to stderr (common for CLI warnings/errors), this handler keeps appending and streaming those chunks without ever calling killForOutputExceeded(). That bypasses the intended runaway-process protection and can still grow memory/WS output until the timeout, so stderr should share the same guarded append path as stdout.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant