feat(tooling): pre-commit hook + skill QA discipline to stop lint/format from reaching CI #206

Closed
opened 2026-04-21 08:44:36 +00:00 by claude-desktop · 0 comments
Collaborator

Goal

Stop Biome formatting and lint errors from reaching CI. Every CI failure on a fleet PR costs a full agent round-trip (action_run_failurefix-ci dispatch → agent turns → commit → push → CI re-run), so cheap upfront checks pay for themselves fast.

Today there is no .husky, no .githooks, no core.hooksPath configured. Every commit goes straight to CI. Fleet dispatches in the last 24h have hit Biome errors multiple times (PR #160, #199 round-2, #178's worktree edits); each required manual operator intervention or an additional agent round.

Two-layer fix

Layer 1 — Git pre-commit hook (husky + lint-staged)

Catches the error on both operator-side commits and agent-side commits. Regardless of who forgot, the commit fails before it can be pushed.

  • Add workspace dev deps: bun add -D husky lint-staged
  • Root package.json gains:
    {
      "scripts": { "prepare": "husky" },
      "lint-staged": {
        "*.{ts,tsx,js,mjs,cjs,json,md}": ["bun x biome check --write --no-errors-on-unmatched"]
      }
    }
    
  • Create .husky/pre-commit containing bun x lint-staged (plus chmod +x).
  • husky install (via the prepare script) runs automatically on every bun install, so:
    • Operator clones: hook installs on bun install.
    • Agent worktree bootstrap (in workdir.ts / skill templates): confirm the worktree runs bun install at acquire time. If it doesn't, add a just setup-hooks step that runs husky install explicitly and call it from workdir.ts::acquireWorktree.
  • lint-staged auto-re-stages the fixed files — important for programmatic agent commits so the resulting commit is already clean.

Layer 2 — Skill-level QA discipline

The hook is the safety net; the skill is the intent. Each skill that commits and pushes should explicitly run just qa before push — so the agent sees errors as part of its own workflow, not as an opaque hook rejection it has to re-reason about.

  • skills/implement.md, skills/implement-delta.md: add a "Before pushing" checklist:
    • - [ ] just qa clean — typecheck + Biome format + Biome lint all pass
    • - [ ] Commit is self-contained — no unrelated changes staged
  • skills/address-review.md, skills/address-review-delta.md: same checklist.
  • skills/fix-ci.md: explicit reminder: "If CI failed on qa, run just qa locally first — don't push a fix that still has pre-existing QA errors."
  • skills/merge.md: already relies on CI-green; no change needed.
  • CLAUDE.md "Conventions" gains a line: "Every commit/push from an agent goes through the pre-commit hook. --no-verify remains forbidden."

Tests / verification

  • just qa has no regressions.
  • Manual smoke: echo 'const x= 1' > /tmp/probe.ts inside the repo, git add, git commit -m test → hook fixes formatting and auto-stages the fix.
  • Manual smoke from inside a running agent container: same flow should work (husky installed, Biome available).
  • CI workflow qa job stays as-is — the hook doesn't replace CI (hook is easy to bypass with --no-verify, CI is authoritative).

Out of scope

  • Pre-push hook. Pre-commit fails fast at the point of mistake; pre-push is too late for agents that move on mentally after each commit.
  • Full typecheck on pre-commit. Too slow (5-10s on this monorepo). Biome format + lint are the cheap, high-value checks. Typecheck stays in CI.
  • Running the test suite on pre-commit. Same reason. Hook stays fast.
  • Blocking --no-verify. Socially enforced via CLAUDE.md; would require a commit-template hack to actually prevent, not worth it.
  • IDE-level format-on-save. Complementary but orthogonal; operator's IDE config.

Dependencies

  • None. Backend + skills + root package.json.

References

## Goal Stop Biome formatting and lint errors from reaching CI. Every CI failure on a fleet PR costs a full agent round-trip (`action_run_failure` → `fix-ci` dispatch → agent turns → commit → push → CI re-run), so cheap upfront checks pay for themselves fast. Today there is no `.husky`, no `.githooks`, no `core.hooksPath` configured. Every commit goes straight to CI. Fleet dispatches in the last 24h have hit Biome errors multiple times (PR #160, #199 round-2, #178's worktree edits); each required manual operator intervention or an additional agent round. ## Two-layer fix ### Layer 1 — Git pre-commit hook (husky + lint-staged) Catches the error on both operator-side commits and agent-side commits. Regardless of who forgot, the commit fails before it can be pushed. - [ ] Add workspace dev deps: `bun add -D husky lint-staged` - [ ] Root `package.json` gains: ```json { "scripts": { "prepare": "husky" }, "lint-staged": { "*.{ts,tsx,js,mjs,cjs,json,md}": ["bun x biome check --write --no-errors-on-unmatched"] } } ``` - [ ] Create `.husky/pre-commit` containing `bun x lint-staged` (plus `chmod +x`). - [ ] `husky install` (via the `prepare` script) runs automatically on every `bun install`, so: - Operator clones: hook installs on `bun install`. - Agent worktree bootstrap (in `workdir.ts` / skill templates): confirm the worktree runs `bun install` at acquire time. If it doesn't, add a `just setup-hooks` step that runs `husky install` explicitly and call it from `workdir.ts::acquireWorktree`. - [ ] lint-staged auto-re-stages the fixed files — important for programmatic agent commits so the resulting commit is already clean. ### Layer 2 — Skill-level QA discipline The hook is the safety net; the skill is the intent. Each skill that commits and pushes should explicitly run `just qa` before push — so the agent sees errors as part of its own workflow, not as an opaque hook rejection it has to re-reason about. - [ ] `skills/implement.md`, `skills/implement-delta.md`: add a "Before pushing" checklist: - `- [ ] just qa` clean — typecheck + Biome format + Biome lint all pass - `- [ ] Commit is self-contained` — no unrelated changes staged - [ ] `skills/address-review.md`, `skills/address-review-delta.md`: same checklist. - [ ] `skills/fix-ci.md`: explicit reminder: "If CI failed on `qa`, run `just qa` locally first — don't push a fix that still has pre-existing QA errors." - [ ] `skills/merge.md`: already relies on CI-green; no change needed. - [ ] CLAUDE.md "Conventions" gains a line: "Every commit/push from an agent goes through the pre-commit hook. `--no-verify` remains forbidden." ### Tests / verification - [ ] `just qa` has no regressions. - [ ] Manual smoke: `echo 'const x= 1' > /tmp/probe.ts` inside the repo, `git add`, `git commit -m test` → hook fixes formatting and auto-stages the fix. - [ ] Manual smoke from inside a running agent container: same flow should work (husky installed, Biome available). - [ ] CI workflow `qa` job stays as-is — the hook doesn't replace CI (hook is easy to bypass with `--no-verify`, CI is authoritative). ## Out of scope - **Pre-push hook.** Pre-commit fails fast at the point of mistake; pre-push is too late for agents that move on mentally after each commit. - **Full typecheck on pre-commit.** Too slow (5-10s on this monorepo). Biome format + lint are the cheap, high-value checks. Typecheck stays in CI. - **Running the test suite on pre-commit.** Same reason. Hook stays fast. - **Blocking `--no-verify`.** Socially enforced via CLAUDE.md; would require a commit-template hack to actually prevent, not worth it. - **IDE-level format-on-save.** Complementary but orthogonal; operator's IDE config. ## Dependencies - None. Backend + skills + root `package.json`. ## References - Biome documentation on staged-only checks: https://biomejs.dev/reference/cli/#biome-check (`--staged` flag). - Husky v9 docs: https://typicode.github.io/husky/ - lint-staged docs: https://github.com/lint-staged/lint-staged - Evidence of the problem in the last 24h: PR #199 round-2 (biome errors on dev's round-3 edits → 7-hour stall + manual operator rebase); PR #160 (similar early in the run). Each cost at least one full agent dispatch. - `skills/implement.md` etc. — current state has no explicit "run QA before push" step.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#206
No description provided.