fix(babysitter): guard factory-created PR activation with durable claims - #484
Conversation
Session-Id: 01a0779f-3f71-7c52-b8ad-cc01ee750ffd
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 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 |
|
@coderabbitai review Requested for exact head |
There was a problem hiding this comment.
2 issues found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/orchestrator/factory.ts">
<violation number="1" location="src/orchestrator/factory.ts:17432">
P1: When mounted PR metadata is unavailable, this condition treats the PR as admissible and can spawn a babysitter without verifying its terminal, draft, or opt-out state. Fail closed and retry/defer the factory handoff until an authoritative snapshot is readable.</violation>
<violation number="2" location="src/orchestrator/factory.ts:17535">
P2: The `factory-created:<prIdentity>` generation claim is never released, and on any post-claim failure the PR is permanently blocked with only a telemetry counter as the signal. In the `catch` block that follows, `#babysitterSession` is cleared but the `markRunning` generation entry is deliberately retained; since `markRunning` returns null for any existing claim without `force`, every later `#ensureBabysitter` for that PR hits `babysitterActivationClaimRejected` and returns. That is intended for the documented "uncertain placement" case, but it also applies to purely transient failures (spawn throw, persist error, injection timeout) that never created a worker, and the only operator-visible trace is an increment to the `babysitterActivationClaimRejected` counter — there is no log line or event naming the repo/PR to guide the required reconciliation. Consider emitting a structured log/event (with the PR identity) whenever a claim is rejected or a spawn fails after claiming, so a transient failure that permanently disables babysitting for a PR is discoverable.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| try { | ||
| const snapshot = await this.#readPrSnapshot(prRef) | ||
| if (snapshot && (this.#babysitterActivationExcluded(prRef.repo, prRef.prNumber, snapshot.labels) || |
There was a problem hiding this comment.
P1: When mounted PR metadata is unavailable, this condition treats the PR as admissible and can spawn a babysitter without verifying its terminal, draft, or opt-out state. Fail closed and retry/defer the factory handoff until an authoritative snapshot is readable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/orchestrator/factory.ts, line 17432:
<comment>When mounted PR metadata is unavailable, this condition treats the PR as admissible and can spawn a babysitter without verifying its terminal, draft, or opt-out state. Fail closed and retry/defer the factory handoff until an authoritative snapshot is readable.</comment>
<file context>
@@ -17414,6 +17428,15 @@ export class FactoryLoop implements Factory {
try {
+ const snapshot = await this.#readPrSnapshot(prRef)
+ if (snapshot && (this.#babysitterActivationExcluded(prRef.repo, prRef.prNumber, snapshot.labels) ||
+ prMetaShowsMerged(snapshot) || snapshot.draft ||
+ (snapshot.state && snapshot.state.toUpperCase() !== 'OPEN'))) {
</file context>
| this.#babysitterIssueRefs.delete(babysitterKey) | ||
| return | ||
| } | ||
| const claim = await this.#state.markRunning( |
There was a problem hiding this comment.
P2: The factory-created:<prIdentity> generation claim is never released, and on any post-claim failure the PR is permanently blocked with only a telemetry counter as the signal. In the catch block that follows, #babysitterSession is cleared but the markRunning generation entry is deliberately retained; since markRunning returns null for any existing claim without force, every later #ensureBabysitter for that PR hits babysitterActivationClaimRejected and returns. That is intended for the documented "uncertain placement" case, but it also applies to purely transient failures (spawn throw, persist error, injection timeout) that never created a worker, and the only operator-visible trace is an increment to the babysitterActivationClaimRejected counter — there is no log line or event naming the repo/PR to guide the required reconciliation. Consider emitting a structured log/event (with the PR identity) whenever a claim is rejected or a spawn fails after claiming, so a transient failure that permanently disables babysitting for a PR is discoverable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/orchestrator/factory.ts, line 17535:
<comment>The `factory-created:<prIdentity>` generation claim is never released, and on any post-claim failure the PR is permanently blocked with only a telemetry counter as the signal. In the `catch` block that follows, `#babysitterSession` is cleared but the `markRunning` generation entry is deliberately retained; since `markRunning` returns null for any existing claim without `force`, every later `#ensureBabysitter` for that PR hits `babysitterActivationClaimRejected` and returns. That is intended for the documented "uncertain placement" case, but it also applies to purely transient failures (spawn throw, persist error, injection timeout) that never created a worker, and the only operator-visible trace is an increment to the `babysitterActivationClaimRejected` counter — there is no log line or event naming the repo/PR to guide the required reconciliation. Consider emitting a structured log/event (with the PR identity) whenever a claim is rejected or a spawn fails after claiming, so a transient failure that permanently disables babysitting for a PR is discoverable.</comment>
<file context>
@@ -17492,6 +17518,31 @@ export class FactoryLoop implements Factory {
+ this.#babysitterIssueRefs.delete(babysitterKey)
+ return
+ }
+ const claim = await this.#state.markRunning(
+ this.#workspaceId, `factory-created:${prIdentity}`, spec.name,
+ this.#clock.now(), DISPATCH_LIFECYCLE_LEASE_MS,
</file context>
Factory-created PR handoffs already reach the babysitter independently of routed discovery, but ignored configured opt-outs and reserved workers by issue identity. Two issue records could therefore activate workers for the same PR.
This change applies PR identity and label exclusions (including the legacy skip-label alias), checks mounted terminal/draft state before activation, and records an atomic repository-and-PR claim before placement. Existing durable sessions also block a second owner. Claims survive session cleanup, restart and lease expiry; an uncertain placement without a recoverable receipt requires operator reconciliation. The existing PR branch is now carried into the worker task even without a shared checkout.
The routed activation guard remains disabled. Babysitter tasks repair checks and review feedback on the existing PR head and retain the no-merge instruction.
Validation:
markRunninghad zero calls. All six pass after the fix.4bbb652. The initial teammate-MCP timeout disappears when inherited broker environment variables are removed.4adfe1c.4adfe1c(package, Kubernetes provider, verification gate, load and verification stack). Verified usinggh run list --branch lane/babysitter-activation-0906.