Bug
When a town uses the PR merge strategy but townConfig.git_auth.github_token is not set (user configured a PAT in github_cli_pat instead, or uses the GitHub App integration token), the poll_pr side effect silently fails on every tick. MR beads with PR URLs stay in_progress permanently. This causes:
- 4 MR beads stuck
in_progress simultaneously (violating the "at most 1 per rig" invariant)
- 604 invariant violations per hour (one per reconciler tick)
- Reconciler polling 3-4 PRs every 5 seconds indefinitely, with ~1.5s wall clock spikes from the failed HTTP calls
- Polecats being re-dispatched for already-completed work — source beads go back to
open via orphan recovery (because MR beads are stuck), and the reconciler assigns new agents to them
Diagnostic Evidence
Analytics Engine data for town 8a6f9375-b806-4ee0-ad6e-1697ea2dbfff shows:
- 604 reconciler ticks in 1 hour, every one with invariant violations
- Most common action pattern:
{"poll_pr": 3} or {"poll_pr": 4} — same PRs polled repeatedly
- 4
agent.dispatch_failed events — polecats dispatched to beads that are already done
- 0
bead.* lifecycle events — no beads are transitioning, everything is frozen
- Wall clock spikes to 1500-1976ms per tick (healthy is <100ms)
Root Cause
checkPRStatus() in Town.do.ts:3493-3583 reads townConfig.git_auth.github_token at line 3501. If this is null or empty, it logs a warning and returns null:
const token = townConfig.git_auth.github_token;
if (!token) {
console.warn(`checkPRStatus: no github_token configured, cannot poll ${prUrl}`);
return null;
}
Back in the poll_pr handler at actions.ts:566:
const status = await ctx.checkPRStatus(action.pr_url);
if (status && status !== 'open') {
ctx.insertEvent('pr_status_changed', { ... });
}
null fails the truthiness check — no event is emitted. The MR bead stays in_progress. On the next tick, reconcileReviewQueue Rule 1 emits another poll_pr. The cycle repeats forever.
The user may have configured git credentials via github_cli_pat (the PAT text input in settings) rather than github_token (the OAuth token from #1350). Or they're using the GitHub App integration token which lives in a different field. checkPRStatus only looks at github_token.
Fix
Fix 1: Fall back to github_cli_pat when github_token is missing
const token = townConfig.git_auth.github_token ?? townConfig.github_cli_pat;
if (!token) {
console.warn(`checkPRStatus: no GitHub token configured, cannot poll ${prUrl}`);
return null;
}
The github_cli_pat is a valid GitHub PAT that can call the GitHub API. It's the same token the container uses for gh CLI operations.
Fix 2: Also try the platform integration token
If neither github_token nor github_cli_pat is set, try resolving the token from the rig's platform_integration_id (the GitHub App installation token). This requires looking up the integration and minting an installation token — more complex but covers the case where the user connected via GitHub App and never set a PAT.
Fix 3: Stop polling when token is missing
If no token is available, the poll_pr action should NOT silently retry every 5 seconds. Instead:
- Emit a
pr_poll_failed event with reason no_token
- After N consecutive failures (e.g., 10), transition the MR bead to
failed with a clear error message: "Cannot poll PR status — no GitHub token configured. Please add a token in town settings."
- Or escalate to the mayor: create an escalation bead that tells the user to configure their git credentials
Fix 4: Rate-limit PR polling
Even when the token is valid, polling 3-4 PRs every 5 seconds is excessive. PR status doesn't change that frequently. Add a per-MR-bead polling interval (e.g., poll each PR at most once per minute) tracked via last_poll_at on the review metadata.
Files
src/dos/Town.do.ts — checkPRStatus() (line 3493-3583)
src/dos/town/actions.ts — poll_pr handler (line 547-576)
src/dos/town/reconciler.ts — reconcileReviewQueue Rule 1 (emits poll_pr)
Acceptance Criteria
Bug
When a town uses the PR merge strategy but
townConfig.git_auth.github_tokenis not set (user configured a PAT ingithub_cli_patinstead, or uses the GitHub App integration token), thepoll_prside effect silently fails on every tick. MR beads with PR URLs stayin_progresspermanently. This causes:in_progresssimultaneously (violating the "at most 1 per rig" invariant)openvia orphan recovery (because MR beads are stuck), and the reconciler assigns new agents to themDiagnostic Evidence
Analytics Engine data for town
8a6f9375-b806-4ee0-ad6e-1697ea2dbfffshows:{"poll_pr": 3}or{"poll_pr": 4}— same PRs polled repeatedlyagent.dispatch_failedevents — polecats dispatched to beads that are already donebead.*lifecycle events — no beads are transitioning, everything is frozenRoot Cause
checkPRStatus()inTown.do.ts:3493-3583readstownConfig.git_auth.github_tokenat line 3501. If this isnullor empty, it logs a warning and returnsnull:Back in the
poll_prhandler atactions.ts:566:nullfails the truthiness check — no event is emitted. The MR bead staysin_progress. On the next tick,reconcileReviewQueueRule 1 emits anotherpoll_pr. The cycle repeats forever.The user may have configured git credentials via
github_cli_pat(the PAT text input in settings) rather thangithub_token(the OAuth token from #1350). Or they're using the GitHub App integration token which lives in a different field.checkPRStatusonly looks atgithub_token.Fix
Fix 1: Fall back to
github_cli_patwhengithub_tokenis missingThe
github_cli_patis a valid GitHub PAT that can call the GitHub API. It's the same token the container uses forghCLI operations.Fix 2: Also try the platform integration token
If neither
github_tokennorgithub_cli_patis set, try resolving the token from the rig'splatform_integration_id(the GitHub App installation token). This requires looking up the integration and minting an installation token — more complex but covers the case where the user connected via GitHub App and never set a PAT.Fix 3: Stop polling when token is missing
If no token is available, the
poll_praction should NOT silently retry every 5 seconds. Instead:pr_poll_failedevent with reasonno_tokenfailedwith a clear error message: "Cannot poll PR status — no GitHub token configured. Please add a token in town settings."Fix 4: Rate-limit PR polling
Even when the token is valid, polling 3-4 PRs every 5 seconds is excessive. PR status doesn't change that frequently. Add a per-MR-bead polling interval (e.g., poll each PR at most once per minute) tracked via
last_poll_aton the review metadata.Files
src/dos/Town.do.ts—checkPRStatus()(line 3493-3583)src/dos/town/actions.ts—poll_prhandler (line 547-576)src/dos/town/reconciler.ts—reconcileReviewQueueRule 1 (emitspoll_pr)Acceptance Criteria
checkPRStatusfalls back togithub_cli_patwhengithub_tokenis missing