fix(review): forward installation token, post inline findings, and stream progress - #57
Conversation
…ream progress The review workflow ran but never posted inline comments. Three issues compounded: - `buildProviderEnv()` in `src/core/executor.ts` did not forward the installation token to the agent subprocess, so `gh`/`git` calls were unauthenticated and silently dropped. - `buildReviewPrompt()` instructed the agent to POST a single blob review via `gh api .../reviews`, which produced one wall-of-text comment instead of per-line findings. - Both review and resolve handlers ran with `skipTrackingComments: true`, so users had no mid-run visibility into agent progress. Fix: - `executor.buildProviderEnv` now accepts an optional `installationToken` and exports it as `GH_TOKEN` + `GITHUB_TOKEN`. `pipeline.runPipeline` threads `ctx.installationToken` into `executeAgent`. - `pipeline` gains `RunPipelineOverrides.trackingCommentId` so handlers can seed the tracking comment up front and pass ownership in. When the caller owns the comment, the pipeline skips its create/finalize calls. - `review.ts` and `resolve.ts` now `setState(...)` before invoking the pipeline, read back the reserved tracking comment id via `findById`, and pass it as `trackingCommentId`. Their prompts gained `[update tracking comment]` checkpoints at every major step so users see live progress. - `buildReviewPrompt` rewritten to require `mcp__github_inline_comment__create_inline_comment` per finding (with `[blocker]`/`[major]`/`[minor]`/`[nit]` severity tags) and explicitly forbids the previous `gh api .../reviews` blob path. - Tests updated: `review.test.ts` mocks `runs-store.findById` and asserts two setState calls (seed + finalize); `dispatcher.test.ts` adds `findById` to its runs-store mock so handler module loading still resolves transitively. Verified end-to-end on PR #54: review #2 posted 3 inline `[minor]` findings, resolve pushed two commits and replied to all three review threads, tracking comment showed mid-run progress through to the final REVIEW.md/RESOLVE.md.
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 30 minutes and 35 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe PR refactors tracking comment lifecycle management to be caller-owned. Workflow handlers now retrieve tracking comment IDs from the runs store and pass them into the pipeline, which conditionally skips automatic comment creation/finalization. The executor accepts an optional installation token for subprocess environment injection, and agent prompts are updated to use MCP tools for live progress updates at explicit checkpoints. Changes
Sequence Diagram(s)sequenceDiagram
participant Handler as Workflow Handler
participant Store as Runs Store
participant Pipeline as runPipeline
participant Executor as executeAgent
participant MCP as MCP Tools
participant GitHub as GitHub API
Handler->>Handler: Seed workflow state<br/>(PR info, check count, etc.)
Handler->>Store: findById(runId)
Store-->>Handler: tracking_comment_id
Handler->>Pipeline: runPipeline(...)<br/>with trackingCommentId
Pipeline->>Executor: executeAgent(...)<br/>with installationToken
Executor->>Executor: buildProviderEnv<br/>inject GH_TOKEN, GITHUB_TOKEN
Executor->>MCP: Run agent with MCP tools
MCP->>GitHub: Create inline comments
MCP->>GitHub: Update tracking comment<br/>at checkpoints
MCP-->>Executor: Progress updates
Executor-->>Pipeline: Agent completion
Pipeline->>Pipeline: Skip createTrackingComment<br/>(caller owns lifecycle)
Pipeline->>Pipeline: Skip finalizeTrackingComment<br/>(caller owns lifecycle)
Pipeline-->>Handler: Result with tracking comment ID
Handler->>Pipeline: Handler-driven finalization<br/>(if needed)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/workflows/handlers/resolve.ts (2)
110-114: Dead=== null/!== nullbranches after?? undefinednarrowing.
seededRow?.tracking_comment_id ?? undefinedalready collapses bothnullandundefined(and a missing row) toundefined, sotrackingCommentIdis typednumber | undefined. The subsequent=== null/!== nullchecks are unreachable and just add noise.♻️ Proposed simplification
const seededRow = await findById(runId); const trackingCommentId = seededRow?.tracking_comment_id ?? undefined; - if (trackingCommentId === undefined || trackingCommentId === null) { + if (trackingCommentId === undefined) { log.warn({ runId }, "resolve handler: tracking comment id not found after seed setState"); } @@ const result = await runPipeline(botCtx, { captureFiles: ["RESOLVE.md"], - ...(trackingCommentId !== undefined && trackingCommentId !== null - ? { trackingCommentId } - : {}), + ...(trackingCommentId !== undefined ? { trackingCommentId } : {}), });Also applies to: 145-150
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/workflows/handlers/resolve.ts` around lines 110 - 114, The code assigns trackingCommentId as const trackingCommentId = seededRow?.tracking_comment_id ?? undefined, which already normalizes null to undefined, so remove the unreachable === null / !== null branches; instead update the checks in the resolve handler to only test for undefined (e.g., trackingCommentId === undefined) and adjust the corresponding log/branch logic accordingly, and apply the same simplification to the other occurrence that references trackingCommentId later in this file.
238-249: Step 6's "ready to merge" message is immediately overwritten by step 10.Both step 6 and step 10 call
update_claude_comment, and the latter "paste full RESOLVE.md" replaces the body wholesale. The user only ever sees the RESOLVE.md content as the final tracking-comment state, so the transient "ready to merge" line never surfaces. Either fold the merge-readiness signal into a required section ofRESOLVE.md(so step 10 carries it), or move the "ready to merge" announcement to a separate channel (top-level PR comment) instead of the same tracking comment that's about to be replaced.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/workflows/handlers/resolve.ts` around lines 238 - 249, The "ready to merge" message posted via update_claude_comment is being overwritten by the RESOLVE.md paste in step 10; update the workflow so the merge-readiness signal survives: either (A) include a clear "Ready to merge" section or flag inside the RESOLVE.md assembled in step 10 (ensure the code that builds RESOLVE.md adds a one-line readiness status when reviewDecision === 'APPROVED' and all checks/comments resolved), or (B) post the one-line readiness as a separate persistent PR comment instead of the tracking comment (use a new call separate from update_claude_comment, e.g., post_pr_comment/update_pr_comment or a distinct update_claude_comment target) so step 10 can still replace the tracking comment with full RESOLVE.md; locate and change the logic around update_claude_comment and the RESOLVE.md assembly to implement one of these options.src/workflows/handlers/review.ts (1)
71-75: Same dead=== null/!== nullbranches asresolve.ts.After
?? undefined,trackingCommentIdisnumber | undefined; thenullchecks are unreachable.♻️ Proposed simplification
const seededRow = await findById(runId); const trackingCommentId = seededRow?.tracking_comment_id ?? undefined; - if (trackingCommentId === undefined || trackingCommentId === null) { + if (trackingCommentId === undefined) { log.warn({ runId }, "review handler: tracking comment id not found after seed setState"); } @@ const result = await runPipeline(botCtx, { captureFiles: ["REVIEW.md"], - ...(trackingCommentId !== undefined && trackingCommentId !== null - ? { trackingCommentId } - : {}), + ...(trackingCommentId !== undefined ? { trackingCommentId } : {}), });Also applies to: 108-113
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/workflows/handlers/review.ts` around lines 71 - 75, The code assigns trackingCommentId via seededRow?.tracking_comment_id ?? undefined which yields a number | undefined, so the subsequent checks like trackingCommentId === null are unreachable; change the condition to check only for undefined (e.g., if (trackingCommentId === undefined) ...) or use a single nullish check (if (trackingCommentId == null) ...) but do not include both === null and === undefined; update the check in the block using findById / seededRow / trackingCommentId and make the identical simplification in the other occurrence that mirrors this logic.test/workflows/handlers/review.test.ts (1)
32-42: LGTM — test mocks and assertions align with the new two-step setState flow.
findByIdreturning a deterministictracking_comment_idmatches the handler's read-back, and the seed/final substring assertions fix the test to the actual call sequence. One small follow-up worth considering: there's no test that exercises thetracking_comment_id == nullwarn path (handler keeps going without a caller-owned id and the pipeline falls back tocreateTrackingComment). Optional — not a regression, but it's the only branch in this handler that isn't covered.Also applies to: 184-194
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/workflows/handlers/review.test.ts` around lines 32 - 42, Add a unit test in review.test.ts that stubs runs-store.findById to return an object with tracking_comment_id: null and asserts that the handler logs/warns about missing tracking id and proceeds to call createTrackingComment; specifically, mock "../../../src/workflows/runs-store" so findById returns id: "run-1" and tracking_comment_id: null, spy on or mock createTrackingComment (the function the handler should call when no tracking id exists), invoke the handler flow that triggers the read-back, and assert that a warning was emitted and createTrackingComment was invoked to cover the tracking_comment_id == null branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/workflows/handlers/review.ts`:
- Around line 199-203: The handler currently calls
formatRefreshDirective(input.staleness) but also enforces a "NEVER push commits"
read-only rule, causing conflicting behavior; fix by choosing one of the two
flows: (a) read-only flow — remove/skip calling formatRefreshDirective in review
handler (src/workflows/handlers/review.ts) and instead append a staleness note
to the generated REVIEW.md (use input.staleness and input.isFork to indicate
state) so the review proceeds against current HEAD without any push; or (b)
abort-on-stale flow — detect staleness via input.staleness (and non-fork), emit
a tracking comment telling the user to run bot:resolve and return early (no
push), so only bot:resolve performs force-pushes; implement one of these,
referencing formatRefreshDirective, REVIEW.md, and bot:resolve, and ensure the
handler never initiates pushes to comply with the "NEVER push commits" (FR-017)
rule.
---
Nitpick comments:
In `@src/workflows/handlers/resolve.ts`:
- Around line 110-114: The code assigns trackingCommentId as const
trackingCommentId = seededRow?.tracking_comment_id ?? undefined, which already
normalizes null to undefined, so remove the unreachable === null / !== null
branches; instead update the checks in the resolve handler to only test for
undefined (e.g., trackingCommentId === undefined) and adjust the corresponding
log/branch logic accordingly, and apply the same simplification to the other
occurrence that references trackingCommentId later in this file.
- Around line 238-249: The "ready to merge" message posted via
update_claude_comment is being overwritten by the RESOLVE.md paste in step 10;
update the workflow so the merge-readiness signal survives: either (A) include a
clear "Ready to merge" section or flag inside the RESOLVE.md assembled in step
10 (ensure the code that builds RESOLVE.md adds a one-line readiness status when
reviewDecision === 'APPROVED' and all checks/comments resolved), or (B) post the
one-line readiness as a separate persistent PR comment instead of the tracking
comment (use a new call separate from update_claude_comment, e.g.,
post_pr_comment/update_pr_comment or a distinct update_claude_comment target) so
step 10 can still replace the tracking comment with full RESOLVE.md; locate and
change the logic around update_claude_comment and the RESOLVE.md assembly to
implement one of these options.
In `@src/workflows/handlers/review.ts`:
- Around line 71-75: The code assigns trackingCommentId via
seededRow?.tracking_comment_id ?? undefined which yields a number | undefined,
so the subsequent checks like trackingCommentId === null are unreachable; change
the condition to check only for undefined (e.g., if (trackingCommentId ===
undefined) ...) or use a single nullish check (if (trackingCommentId == null)
...) but do not include both === null and === undefined; update the check in the
block using findById / seededRow / trackingCommentId and make the identical
simplification in the other occurrence that mirrors this logic.
In `@test/workflows/handlers/review.test.ts`:
- Around line 32-42: Add a unit test in review.test.ts that stubs
runs-store.findById to return an object with tracking_comment_id: null and
asserts that the handler logs/warns about missing tracking id and proceeds to
call createTrackingComment; specifically, mock
"../../../src/workflows/runs-store" so findById returns id: "run-1" and
tracking_comment_id: null, spy on or mock createTrackingComment (the function
the handler should call when no tracking id exists), invoke the handler flow
that triggers the read-back, and assert that a warning was emitted and
createTrackingComment was invoked to cover the tracking_comment_id == null
branch.
🪄 Autofix (Beta)
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: defaults
Review profile: CHILL
Plan: Pro
Run ID: bd4bd5f9-5cea-4ddd-b5c2-d108547f704d
📒 Files selected for processing (6)
src/core/executor.tssrc/core/pipeline.tssrc/workflows/handlers/resolve.tssrc/workflows/handlers/review.tstest/workflows/dispatcher.test.tstest/workflows/handlers/review.test.ts
…ment handoff The doc-sync guard (FR-019) flagged the review/resolve handler changes as needing a matching `docs/BOT-WORKFLOWS.md` update. Both sections now reflect: - `review` posts each finding via `mcp__github_inline_comment__create_inline_comment` (one MCP call per finding) instead of a single bundled `gh api .../reviews` POST. - Both handlers seed the tracking comment via `setState` before invoking the pipeline and pass the reserved id as `RunPipelineOverrides.trackingCommentId` so they own the comment lifecycle and can post `[update tracking comment]` progress checkpoints mid-run. - Authentication: the bot's shell `gh`/`git` calls use the installation token forwarded as `GH_TOKEN`/`GITHUB_TOKEN` by `buildProviderEnv` in `src/core/executor.ts`.
… and step 12
CodeRabbit flagged that step 12 ("NEVER push commits") contradicted step 1
("After rebase + push, the head SHA changes…") and `formatRefreshDirective`,
which emits explicit `git push --force-with-lease` instructions when the
branch is stale and not a fork. The agent's behavior was undefined on stale
non-fork PRs.
Resolution: clarify step 12 so a clean rebase+force-push is permitted (same
diff, fresh head SHA), but new code-bearing commits, merges, and
APPROVE/REQUEST_CHANGES verdicts remain forbidden. This matches the
documented Branch refresh policy in `docs/BOT-WORKFLOWS.md` (review rebases
stale branches; only resolve creates new commits).
Tightened the doc's review Stop conditions bullet to spell out the push
policy explicitly so the rule is documented in one place.
# [1.3.0](v1.2.2...v1.3.0) (2026-04-25) ### Bug Fixes * **review:** forward installation token, post inline findings, and stream progress ([#57](#57)) ([7ee4861](7ee4861)) * **workflows:** make end-to-end runs survive without mid-run caps or stale state ([#55](#55)) ([35ee605](35ee605)) ### Features * **workflows:** add label-dispatched bot workflow foundation ([#49](#49)) ([1b18779](1b18779))
|
🎉 This PR is included in version 1.3.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
The review workflow ran but never posted inline review comments. Three issues compounded into the same symptom and are fixed together:
buildProviderEnv()insrc/core/executor.tsdid not export the token, sogh/gitcalls inside the multi-turn agent ran unauthenticated and silently dropped.gh api .../reviews, producing one wall-of-text comment on the PR instead of per-line findings.reviewandresolvehandlers ran withskipTrackingComments: true, so users got no mid-run progress visibility — just a final report (or, in the failure case above, nothing at all).What changed
src/core/executor.ts—buildProviderEnv()now accepts an optionalinstallationTokenand exports it asGH_TOKEN+GITHUB_TOKEN.ExecuteAgentParamsgains the same field.src/core/pipeline.ts—runPipelinethreadsctx.installationTokenintoexecuteAgent. AddsRunPipelineOverrides.trackingCommentIdso handlers can seed the tracking comment up front and pass ownership in. When the caller owns the comment, the pipeline skips its own create/finalize calls (both success and error paths).src/workflows/handlers/review.ts— Handler nowsetState(...)s a "Code review starting" message before invoking the pipeline, reads back the reserved tracking comment id viafindById, and passes it astrackingCommentId.buildReviewPromptrewritten to requiremcp__github_inline_comment__create_inline_commentper finding (with[blocker]/[major]/[minor]/[nit]severity tags) and explicitly forbids the previousgh api .../reviewsblob path. Five[update tracking comment]checkpoints stream progress.src/workflows/handlers/resolve.ts— Same seed/handoff pattern with a "🔎 Resolve starting" message. Prompt notes thatgh/gitare pre-authenticated as the GitHub App installation, lays out a 10-step procedure with checkpoints, and usesgh api .../comments/<id>/repliesto reply on review threads.test/workflows/handlers/review.test.ts— Mocksruns-store.findById(handler now imports it) and asserts twosetStatecalls: seed (Code review starting…) + finalize (Code review complete…).test/workflows/dispatcher.test.ts— AddsfindByIdto theruns-storemock so the dispatcher's transitive handler module loading still resolves.Verification
End-to-end on PR #54 (
refactor/shared-test-factories):[minor]findings posted at the right files/lines via MCP, tracking comment 4319228452 streamed mid-run progress through to final REVIEW.md.MERGEABLE/BLOCKEDstate — blocked only on human review (per FR-017 the bot never APPROVEs).Test plan
bun test test/workflows/handlers/review.test.ts— 7 passbun test test/workflows/dispatcher.test.ts— module loads + all asserts passbash scripts/test-isolated.sh— same pre-existing failures asmain(test/db/migrate.test.ts,test/orchestrator/liveness-reaper.test.ts); no regressions from this PR🤖 Generated with Claude Code
Summary by CodeRabbit