fix(gate): take the host validation lock for heavy gates

Merged
TH
4h ago
+494-79

Summary

pnpm gate unit-ci (and other whole-repo gates) did not take the host validation lock. Four worktrees ran it at once on a 12-core Mac; load reached ~1000 and every run hit false 5 s test timeouts.

pnpm gate <id> now runs heavy gates under the same lock that check:affected --run and check hold: same owner, waiter output, dead-owner reclaim, and 2-hour timeout. No second lock.

  • Locked (heavyGate in CHECK_CATALOG, hostLocked): unit, unit-ci, provider-integration (whole-repo suites), fuzz-parsers, mutation, mutation-affected, concurrency-torture (many workers/processes). Cheap gates stay unlocked.
  • Re-entry: a gate run inside a locked validation sees the inherited AGENT_DEVICE_VALIDATION_PARENT and runs directly, so nothing deadlocks.
  • CI: GITHUB_ACTIONS=true runs the gate unlocked (single-tenant runners). Unsupported hosts (Windows) also run unlocked.
  • acquireValidationLock takes an optional env, so nested-owner detection reads the same environment as the gate runner.
  • Docs: AGENTS.md and docs/agents/testing.md name the locked gates. To stay inside the guidance byte budgets, I removed a few duplicate sentences in testing.md/pull-requests.md.

The lock protocol (publish, activate, finish) and the signal-to-exit-code mapping have one owner in scripts/check-affected: runLockedCommand and abortOnTerminationSignal. Validation runs and gates both use them.

No linked issue.

Validation

  • Tested SHA: 1628711576.
  • pnpm check:affected --run --head 1628711576 exits 0 with exactCommit: true (all runnable checks passed, including lint on the merged origin/main).
  • scripts/gate/host-locked.test.ts (7 tests) and host-lock.test.ts (4 tests) pass. They cover waiting, the exit code, nested no-deadlock, unlocked cheap and GitHub gates, a signal while waiting (143), a signal while the locked command runs (143, command group gone, lock and active.json removed), and runLockedCommand publishing and clearing ownership.
  • pnpm gate unit-ci waited behind another owner, then ran through the shared runLockedCommand path: exit 0, 16977 tests passed, lock released.
  • CI owns full coverage, integration, and native lanes.

🤖 Generated with Claude Code