Skip to content

refactor: nuclear-review pass — hook-command unification, installer dedup, Codex-bridge hardening#61

Merged
arzafran merged 9 commits into
mainfrom
refactor/nuclear-review-pass
Jun 19, 2026
Merged

refactor: nuclear-review pass — hook-command unification, installer dedup, Codex-bridge hardening#61
arzafran merged 9 commits into
mainfrom
refactor/nuclear-review-pass

Conversation

@arzafran

Copy link
Copy Markdown
Member

What this does

A whole-codebase cleanup pass from /nuclear-review plus a set of Codex-bridge fixes. Three things a user/maintainer would actually feel:

  1. The Codex bridge stops getting wedged. A single misclassified "quota exceeded" error used to disable the bridge for ~5h across every project with no way out; now --force overrides it, repeated available checks don't re-spawn codex login status every call, timeouts say what happened instead of an opaque exit code, and exec shows you the diff it made.
  2. The hook tamper-defense is harder to fool and easier to reason about. Three slightly-different definitions of "is this a managed ~/.claude/src hook command" (which had already drifted apart on whether lib/ counts) are now one module, tightened to the safe (scripts|hooks) set.
  3. A latent install bug is fixed. 7 current skills were missing from the managed list, so a full→light reinstall left them behind. They're added, and lint:skills now fails if the list ever drifts from skills/ again.

No behavior change for a normal install — the refactors are behavior-preserving and the full suite (594 tests) stays green.

Summary

  • Codex bridge (codex.ts, codex-run.ts): --force escape past sticky rate-limited/no-access; 60s positive-TTL skips the per-call login probe; explicit timeout reporting w/ partial output; exec appends git status/diff --stat; sanitizeOutput strips ANSI + redacts sk-/Bearer/Authorization/*_API_KEY|TOKEN|SECRET= on all returned output.
  • Hook-command unification: new src/lib/hook-command.ts (parseHookCommand/isManagedHookCommand/MANAGED_HOOK_CMD/iterCommandHooks) replaces the three classifiers in settings-merge/light-profile/audit-hooks; trusted regex tightened (scripts|hooks|lib)(scripts|hooks); traversal goes through the HooksBlock schema. audit-hooks findings are now a HookFinding|SchemaFinding union.
  • Fingerprint/manifest hardening (hooks-fingerprint.ts): zod-validated readFingerprint; readSrcManifest rejects ../absolute keys + non-string values; control chars stripped from installedAt before the banner echoes it.
  • Installer (setup.ts): buildInstallPlan single source of truth; main() split into runMigrateOnly/runFullInstall; redundant skill-prune loop removed; managed-skills.ts split into ACTIVE_SKILLS (now complete) + TOMBSTONE_SKILLS; lint:skills asserts ACTIVE_SKILLS == skills/.
  • Settings merger is now pure: MCP preservation moved to mcp.ts resolveMcpServers (settings-merge.ts no longer imports mcp.ts).
  • Internal: claude-audit.ts split into model+render; shared frontmatter-lint core extracted; atomic writeState; normalizeCounterState.

Reviewed cross-model: an independent security-reviewer pass and a Codex review of the diff both approved it; their hardening findings are folded in (the harden(security) commit).

Test Plan

  • bun run typecheck — clean
  • bun test — 594 pass / 0 fail (+10 new: redaction forms, manifest path-traversal rejection, control-char strip)
  • bun run lint — clean
  • bun run audit:hooks — 27 trusted, 0 stale/unknown/suspicious
  • bun run schemas:check — in sync
  • install-e2e full→light switch — passes (the bug this caught)

arzafran added 9 commits June 19, 2026 14:00
…ing, exec diff, output sanitization

- --force bypasses a sticky rate-limited/no-access verdict (Codex emits
  'quota exceeded' on auth mismatch too, so a misfire shouldn't disable the
  bridge globally for 5h with no escape)
- skip the per-call 'codex login status' spawn when a fresh 'available'
  verdict is <60s old (AVAILABLE_TTL_MS)
- detect timeouts explicitly and surface partial output + a split/raise hint
  instead of an opaque 'exit N'
- exec appends git status/diff --stat so the caller always sees what Codex
  changed
- sanitizeOutput() (ANSI + secret redaction) now runs over returned output,
  not just the cached stderr detail
- writeState now writes tmp+rename (was a bare writeFile mislabelled
  'Atomic-ish'); a crash mid-write can no longer corrupt counter/queue state
- CounterState rehydration goes through normalizeCounterState(unknown),
  removing the inline re-defaulting and the 'as number | undefined' cast;
  always-present fields are now required on the interface
analyzeCommands(label, files) -> AuditModel (pure aggregation) and
renderAudit(model) -> string (pure formatting). New report sections become
data-only; output is byte-identical for the same input.
lint-skills and lint-knowledge shared ~80% scaffolding (dir walk, missing
frontmatter, yaml errors, name mismatch). lintFrontmatterCore() owns the
shared path; each linter keeps only its domain rules. Rule keys and messages
unchanged.
Single src/lib/hook-command.ts (parseHookCommand / isManagedHookCommand /
MANAGED_HOOK_CMD / iterCommandHooks) replaces three divergent definitions in
settings-merge, light-profile, and audit-hooks. The trusted regex is unified to
(scripts|hooks) only — drops lib/ from audit's TRUSTED_BUN_CC (no shipped hook
invokes lib/; tightening the trusted surface is the safe direction). Hook-block
traversal goes through HooksBlock-schema-driven iterCommandHooks instead of
three hand-rolled walks.

Also: delete the teamRaw alias; model audit findings as a HookFinding|SchemaFinding
discriminated union (no more sentinel-string matching); validate the fingerprint
record through zod in readFingerprint. canonicalize()/hash unchanged so existing
fingerprints stay valid.

Security: lib/ commands now classify as untrusted (1 flipped test); fail-open
preserved; [ \t] arg-separation kept (not widened to \s).
…LLS + lint guard

- buildInstallPlan(sourceDir, profile) is the single source of truth for
  disk-touching install/prune steps; install and light-incompatible removal
  derive from it instead of re-walking PROFILE_MANIFEST
- main() splits into runMigrateOnly / runFullInstall behind a thin dispatcher;
  full-install phase order preserved exactly (clean before copy, fingerprint
  after settings write, manifest for tamper defense)
- removed the redundant in-installer skill-prune loop — BUT it was masking a
  real gap: 7 current skills (codex, freeze, plan-ceo-review, proof-of-work,
  retro, review-batch, strategist) were missing from the managed list, so
  cleanOldConfig didn't prune them on a full→light switch (caught by install-e2e)
- split managed-skills into ACTIVE_SKILLS (35, complete) + TOMBSTONE_SKILLS;
  MANAGED_SKILLS = union, consumers unchanged
- lint:skills now asserts ACTIVE_SKILLS matches skills/ on disk (opt-in,
  repo-run only) so this drift becomes a lint error, not silent install drift

Deferred: MCP-preservation boundary move (Change 4) — making the merger pure
requires changing its API + rewriting MCP-preservation tests; not a pure
behavior-preserving refactor. Tracked separately.
settings-merge.ts is now a pure JSON merge (renamed mergeSettings) that takes a
pre-resolved mcpServers value and no longer imports mcp.ts. The MCP-preservation
semantics move to mcp.ts as resolveMcpServers(user, team); the
mergeSettingsWithMcpPreservation wrapper now lives there and composes
resolveMcpServers + mergeSettings. Observable behavior is unchanged — tests only
move their import source; every preservation assertion (which servers survive,
precedence, _status) is identical.
From the security-reviewer + Codex gate on this branch:
- readSrcManifest rejects manifest keys with ..\/absolute paths and non-string
  hash values (the manifest is the Shai-Hulud-targeted artifact; keys are
  join()'d under ~/.claude/src and read)
- strip C0/C1 control chars from fingerprint + manifest installedAt before it's
  echoed into the warning banner (ANSI-injection guard)
- sanitizeOutput also redacts *_API_KEY|TOKEN|SECRET= assignments and Bearer:
  (colon, no space) forms
- document why classifyCompound's all-parts-managed check must never relax to
  some() (|| branches still run)
Adds 10 tests covering the new redaction forms + manifest validation + control strip.
Three files said 34 skills; there are 35 (freeze uncounted). lint:skills now
enforces ACTIVE_SKILLS == skills/ so the count can't silently drift again.
@arzafran arzafran merged commit 94bec6e into main Jun 19, 2026
15 checks passed
@arzafran arzafran deleted the refactor/nuclear-review-pass branch June 19, 2026 20:37
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