fix(engine): cap verification-failure bounces, reap unregistered worktrees, dedupe activity log

Three fixes for the worktree-overflow / stuck-task incident:

1. Cap deterministic-verification-failure bounces (fix #2)
   Auto-merge previously bounced an in-review task back to in-progress
   on every verification failure with no upper bound. A single flaky test
   could keep a task ping-ponging in-review→in-progress forever, holding
   its worktree and consuming agent slots. Adds verificationFailureCount
   on Task (DB migration v48), increments on each bounce, and after 3
   failures marks the task failed and creates a follow-up triage task
   so a fresh agent can investigate the underlying flake instead of
   re-running the same fix loop.

2. Reap unregistered orphan worktree dirs even when recycle is on (fix #3)
   cleanupOrphans previously bailed out entirely when recycleWorktrees
   was true, leaving stale dirs (clear-hawk-broken, *-bak, leftover
   crash debris) on disk forever. New reapUnregisteredOrphans pass
   removes only directories that aren't registered git worktrees, so
   the recycle pool keeps its warm worktrees but the trash gets cleared.

3. Idempotence guard on activity-log listener wiring (fix #6)
   setupActivityLogListeners() was registering handlers on every call.
   When init() ran twice, every task:created / task:moved event wrote
   N rows to activityLog, producing the duplicate entries visible in
   the DB. Added activityListenersWired flag so repeated calls no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-26 21:09:28 -07:00
parent 07e86e696d
commit 53decd1bd2
12 changed files with 237 additions and 42 deletions

View File

@@ -1836,6 +1836,15 @@ export class Database {
});
}
// Outer verification-failure bounce counter — counts in-review→in-progress
// returns triggered by VerificationError. Capped to prevent infinite
// re-merge loops on flaky tests (see project-engine.ts auto-merge handler).
if (version < 48) {
this.applyMigration(48, () => {
this.addColumnIfMissing("tasks", "verificationFailureCount", "INTEGER DEFAULT 0");
});
}
}
/**