Skip to content

fix(babysitter): guard factory-created PR activation with durable claims - #484

Merged
kjgbot merged 1 commit into
mainfrom
lane/babysitter-activation-0906
Sep 6, 2026
Merged

fix(babysitter): guard factory-created PR activation with durable claims#484
kjgbot merged 1 commit into
mainfrom
lane/babysitter-activation-0906

Conversation

@kjgbot

@kjgbot kjgbot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

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:

  • Six admission regressions failed before the fix: opt-outs still spawned, and markRunning had zero calls. All six pass after the fix.
  • A separate existing-head handoff assertion failed before its fix and passes afterward.
  • Final focused suite: 108 passed across orchestrator, task-template, state-store and routed-discovery coverage.
  • Build, feature-map validation and package dry-run passed.
  • Full suite on final head with broker environment isolated: 2,555 passed, 1 failed, 1 skipped. The only failure is the existing heartbeat timing/cleanup test; it also fails on unmodified base commit 4bbb652. The initial teammate-MCP timeout disappears when inherited broker environment variables are removed.
  • Packed E2E: 6 run-cost tests and all 12 packed-consumer lifecycle checks passed, with attestation bound to 4adfe1c.
  • CI run 34047387813: all five jobs passed on exact head 4adfe1c (package, Kubernetes provider, verification gate, load and verification stack). Verified using gh run list --branch lane/babysitter-activation-0906.

Session-Id: 01a0779f-3f71-7c52-b8ad-cc01ee750ffd
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 98f6472f-c517-40c1-ac50-ce88872b558e


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.

@kjgbot
kjgbot marked this pull request as ready for review September 6, 2026 17:11
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review

Requested for exact head 4adfe1c6ee54c83b9ee641481e9359b7a95d95f3.

@cubic-dev-ai cubic-dev-ai 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.

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) ||

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: 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(

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: 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>

@kjgbot
kjgbot merged commit 1f8e550 into main Sep 6, 2026
9 checks passed
@kjgbot
kjgbot deleted the lane/babysitter-activation-0906 branch September 6, 2026 17:26
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.

1 participant