Skip to main content

Appendix E — Quality Gates and Hooks

Hooks provide useful local feedback and bounded blocking. They are not an unbypassable security boundary or proof that an entire project is correct. This appendix describes the shipped reference adapters, their installation checks and their limitations. Use the executable files in the kit instead of maintaining separate copied scripts from prose.

Hooks are named by purpose, not sequence. Historical anchors and installed filenames remain compatible. Operating model, testing reference.

Hook nameMaintained scriptBehavior
Post-edit lint checklint-on-edit.shAdvisory, read-only lint within its supported file scope
Session-end verification reminderverify-before-stop.shDebug-log reminder; does not execute or verify tests
Pre-push quality gatepre-push-gate.shBlocks supported push tool calls on configured gate failure
Documentation freshness checkcheck-doc-staleness.shAdvisory age/metadata check; does not verify factual accuracy
Memory budget checkmemory-budget-check.shAdvisory size/entry hygiene check; does not rewrite memory

The settings template uses these descriptive names as statusMessage values for its four registered handlers. Claude Code documents this field as the spinner message while a hook runs; it is presentation, not a control or execution receipt. Existing project settings are preserved by the installer, so adopt the labels through a reviewed merge. The documentation freshness script still requires an explicit document directory and project-specific registration; this naming change does not activate an additional hook. See hook handler fields.

Table of Contents

1. 3-Layer Defense Architecture

Three local hook layers complement a fourth, server-side merge/release layer. Each observes a defined scope; none inherits another layer's passing status.

Text equivalent: local edits can produce diagnostics; the shipped Stop adapter writes a debug-log reminder; a recognized push can be blocked. Model-visible feedback requires a supported, host-tested delivery mechanism. All delivery paths still need effective server controls and a distinct release decision.

Layer Characteristics

LayerObserved scopeWhat it does not establish
Post-edit lint checkRecognized file/tool events and configured lint scopeComplete-project quality or rollback of the edit
Session-end verification reminderSelected local change indicatorsWhether required tests actually executed or passed
Pre-push quality gateRecognized command forms and configured checksOther actors, all shell syntax, business approval or release
ServerExact required contexts, branch rules and approval policyControls it does not require, bypass prevention beyond its configuration, business truth

Measure latency in the adopter's environment. Previous fixed sub-second/ten-second claims were illustrative, not guarantees.

Why Three Layers

Early feedback reduces avoidable delay. A reminder can help completion discipline. Pre-push checks catch recognized failures before publication. These complementary benefits do not make the layers independent or exhaustive; required server checks and protected acceptance criteria remain necessary.

2. Hook Type Reference

These are the events used by the Claude adapter, not an exhaustive list of events supported by every runtime. Verify the installed runtime's documented contract. The official hooks reference distinguishes blocking exit 2 from other nonzero errors; do not translate a generic shell failure into assumed tool denial.

PreToolUse

Runs before a supported tool call. Validate the actual event payload and tool input; a missing/malformed required field is not a clean assessment. For this adapter, use exit 2 and an actionable, sanitized diagnostic to block. Exit 1 is an execution error, not the blocking convention taught by older examples.

The kit's templates/hooks/pre-push-gate.sh is the maintained implementation. A passing shell invocation alone does not prove that the host invoked it or respected the result—test the installed integration.

PostToolUse

Runs after the supported tool has executed. Feedback cannot undo that action. The supplied lint adapter runs read-only checks on resolved Python paths within the installed project's backend/app or backend/tests. It rejects paths resolving outside those directories, including symlink escapes. Its exit-0 plain output is debug-log-only under the documented Claude contract, not a delivered message to the model. Host-test a supported structured output mechanism before claiming interactive feedback; keep mandatory comprehensive assessment in CI.

Stop

The supplied templates/hooks/verify-before-stop.sh is a diff-aware, debug-log-only command: plain stdout with exit 0 does not enter Claude's context. It does not collect receipts, validate all expected tests, deliver a verified model reminder or force verification. Other runtime hook types may have different capabilities; this claim is about the shipped adapter.

Post-edit lint check

Use immediate, bounded checks selected for the project's stack. Ensure the path under inspection is the actual edited path and lies within the declared project; a failed directory change must not run checks in another repository.

settings.json Configuration

Start from templates/settings.json and the hook paths installed by setup/bootstrap.sh. The installer preserves existing settings. Review and test the merged configuration; copying a template does not activate it automatically.

Maintained script: lint-on-edit.sh

Use templates/hooks/lint-on-edit.sh. The old lint-edited.sh example name is retired; its incoming anchor remains. Inspect the maintained script's supported file/path scope and configure equivalent coverage for the adopter. The former inline implementation is retired to avoid a second, divergent executable source.

Design Decisions

Separate advisory feedback from mandatory evidence. Do not hide missing executables or parse failures behind a report of “clean.” A quick production-file check does not establish that test, configuration and script changes were assessed. Comprehensive profile checks cover those separately.

Extending Layer 1 for Frontend

Select the existing project's TypeScript/JavaScript lint and type commands. Use installed, pinned tools rather than an implicit package download. Add fixtures for relevant paths, spaces, malformed events, missing tools and intentional lint failures. No generic frontend adapter is claimed simply because this extension procedure is documented.

Session-end verification reminder

This layer is a proposed reminder point. The shipped adapter only writes a debug-log message; it is not an autonomous approval or completion engine.

settings.json Configuration

Register the installed verify-before-stop.sh as the reference command hook. Review the current settings template and runtime documentation for syntax and timeout support. Preserve unrelated permissions and hooks.

How the Stop Hook Works

When its declared change detection applies, the script prints a verification reminder and exits successfully. The runtime stores this plain output in debug logs, not model context. The engineering task still needs explicit evidence: revision, command, environment, non-empty required selection, outcomes and known gaps. Neither the message nor the hook's successful exit is that evidence.

Reminder limits

No comparative power claim is supported. Timely feedback is a design goal, not a demonstrated property of this adapter. Even a successfully delivered reminder could be ignored and would not establish execution.

Designing Effective Stop Prompts

Keep reminders short and specific to the adopted profile. Point to actual verification commands and evidence expectations. Do not require raw conversation logs or hidden model reasoning. Avoid repeated prompts on discussion-only turns and any loop that pressures the model to invent completion evidence.

Per-Project Stop Hook Variants

Select backend, frontend, documentation or mixed checks according to actual changes and the project's test policy. Local feedback may be targeted; CI/release must provide the comprehensive required evidence. If a coverage threshold is referenced, use the approved value and scope, not an approximate aggregate.

Enhanced Stop Hook — Test Mapping

A future receipt-aware completion service could map changed artifacts to expected tests and verify execution identity/results. The supplied Stop script does not implement this. A mapping file or a test's presence is not proof it ran; omitted and empty selections require explicit handling.

Common Stop Hook Mistakes

  • Treating a successful advisory hook as a passed test suite.
  • Assuming a prompt forces re-verification.
  • Recommending a lint-rule subset as if it matched comprehensive CI.
  • Suppressing errors from supposed mandatory checks with || true.
  • Depending on hook ordering that the installed runtime does not guarantee.

Pre-push quality gate

This local adapter blocks a recognized tool call when its configured checks fail or required configuration is absent. Human commands, aliases, nested scripts and other paths need server controls.

settings.json Configuration

Use the installed PreToolUse adapter with the supported shell-tool matcher. Set the actual source root and executable project test command. Test registration using a harmless deliberately failing check; do not push to a real remote merely to test the adapter.

Hook Script: pre-push-gate.sh

The maintained implementation is templates/hooks/pre-push-gate.sh; regressions live in tests/run-checks.sh and tests/test_tooling_safety.py. It requires the event's absolute cwd to resolve to the installed project root. Only a standalone direct git push is supported. Recognized pushes using Git global options, wrappers, assignments or compound commands are blocked rather than checking the wrong repository. Repository-selecting environment overrides also block. The configured source root selects the lint/test subtree, not a different push target. Aliases and nested interpreters still require server controls; this is not a complete shell security parser.

Extended Pre-Push Gate

Put additional profile checks in one maintained project gate with explicit test, lint, type and build scope. Set the PR template's S4U_PROJECT_GATE step environment value to that reviewed repository-relative executable. The template does not create or assume a script: an empty selection or absent/nonexecutable file fails as unconfigured, and a selected gate's failure propagates. Coverage and non-empty expected selections need real runner configuration; do not add successful echo placeholders.

Design Decisions

A mandatory gate cannot authorize a push because its root, command or evidence is unknown. Distinguish assessed failure from inability to assess in the diagnostic while blocking the affected operation. A pre-push parser is a convenience filter, not a security parser for every shell grammar.

Blocking Destructive Operations

Use the runtime sandbox/permission system and repository protections as the actual authority boundaries. An illustrative substring test for force-push or hard-reset is bypassable and is no longer supplied as a security control. Destructive actions still require exact authorized targets, preservation/recovery considerations and a tested operation path.

Subagent Blind Spot

Inheritance and hook support are runtime/version-specific; do not assume either universal inheritance or universal absence. Test the agent mode actually used. A controller must inspect returned artifact identity and required evidence. Prompting a worker to run checks is not a substitute for those results or for server-side enforcement.

Security Scanning Hooks

Use the project's approved scanners and protected CI review. Confirm code/data handling before any external upload. An unavailable or unauthorized scanner remains unassessed; local checks do not automatically provide equivalent assurance. Bound remediation attempts, preserve findings and obtain authority for material policy or scope changes.

Documentation freshness check

templates/hooks/check-doc-staleness.sh <docs-dir> [window-days] reports supported document verification dates and unassessed metadata. The default window is 30 days. It is advisory, read-only and not automatically registered in templates/settings.json: the adopter must choose the actual document scope and verify delivery. Calendar age is not semantic truth or evidence of deployment. See architecture documentation checks.

Memory budget check

templates/hooks/memory-budget-check.sh checks the selected memory hub's byte budget and entry hygiene without rewriting it or exposing entry contents in diagnostics. The settings template registers it on Stop; its zero exit and plain output are advisory/debug-only, not proof of memory loading or a blocker. See memory discipline.

6. Hook Configuration Reference

Complete settings.json Structure

The single executable configuration source is templates/settings.json, not a second copy embedded here. The current template uses reference hook paths and must be reviewed against the adopter's existing settings. Installation status, configuration status and runtime verification are separate records.

Configuration Fields

Validate supported event, matcher, hook type, executable command and timeout fields against the installed runtime. Reject incompatible configuration rather than assuming a field was honored. Do not carry historical claims that prompt hooks support only one event or that every runtime exposes exactly three events.

Hook File Placement

The reference installer places scripts under .claude/hooks/ and leaves edited files intact, offering upgrade candidates. Resolve execution root explicitly, preserve executable mode and test paths containing spaces. Do not overwrite an existing configuration to make a sample match.

setup/bootstrap.sh uses its adjacent setup/bootstrap.py (Python 3.9+ standard library). Run the entry point from a complete, pinned kit checkout; copying the shell wrapper alone is not an installation. Dry-run previews selected writes without creating a lock or files. The installer preserves existing CLAUDE.md, settings, local bytes and POSIX modes; its legacy checksum receipt does not establish ownership of permissions. Edited upgrade proposals receive another numbered candidate instead of being overwritten. Supported selected inputs are regular, single-link files; symlinks, special files and malformed ownership receipts require explicit resolution.

Text equivalent: planning and backups precede installation. Precommit failure or a handled interruption restores prior bytes and modes where safe. A conflicting edit is preserved and recovery requires review. Once committed, cleanup failure is a different outcome: the installed version remains new and must not be rolled back from incomplete backups. This is process-level recovery for controlled local directories, not atomic multi-file visibility. Quiesce consumers and other writers during upgrade. ACLs, extended attributes, file ownership, machine failure and hostile-filesystem containment are outside the stated guarantee.

A remaining project-root .s4u-install.lock refuses retry. First establish whether the installer is still running. If stopped, inspect journal.json, the actual installed files and any payloads/*.original/*.proposed entries; journal state alone after an abrupt machine/process failure is insufficient. Reconcile only listed paths and preserve intervening edits. For committed or rolled_back with incomplete cleanup, confirm the corresponding installed state before removing the residual installer-owned data. Do not blindly delete a lock or use a recursive project-root cleanup. The older .claude/.s4u-install.lock is also refused by a real installation and needs separate inspection.

Multiple Hook Groups

Do not rely on undocumented sequential execution for dependent checks. If ordering is essential, invoke them through one checked orchestrating command with explicit failure propagation. Prevent overlapping writes and shared predictable temporary paths.

7. Integration with Superpowers

Skills guide procedure. Hooks assess a configured subset or provide reminders. Neither supplies business authority or guarantees correct outcomes.

The Verification Overlap

MechanismContributionLimit
Verification skillInstructs the agent to gather and report evidenceInstruction is not execution
Stop reminderPrompts attention to missing verificationNo receipt collection or enforced re-run
Test/build gateExecutes declared checksResult is limited to selection and oracle
Protected review/releaseApplies authorized decisions to exact artifactsRequires verified configuration and accountable owners

Lifecycle Integration Points

Text equivalent: the lifecycle produces checked artifacts and a receipt; hooks support the work but cannot replace independent review or release authority.

When Skills and Hooks Disagree

Actual output can contradict an unverified claim, but a hook does not outrank binding policy. Resolve differences against approved authority and applicable controls; preserve evidence of implementation drift. A reminder that mentions tests is not evidence that tests are absent or present.

8. Creating Custom Hooks

Step-by-Step Guide

  1. Define the risk, applicable subject set, owner and expected evidence.
  2. Select a supported runtime event and document what it cannot observe.
  3. Reproduce the failure in a disposable fixture before implementing the hook.
  4. Validate input and configuration; use argument arrays/quoting rather than injected shell source.
  5. Register the adapter without overwriting unrelated settings.
  6. Test known pass, known fail, missing dependency, malformed input, empty selection and timeout/cancellation.
  7. Verify actual host invocation and blocking semantics separately from script exit status.
  8. Record cost, limitations, installation version and retirement criteria.

Best Practices

Fail-Open vs. Fail-Closed

Advisory feedback can leave editing available while clearly reporting an unassessed check. Mandatory authorization or release gates block on absent configuration, inaccessible evidence and execution failure. For the Claude PreToolUse adapter, blocking is exit 2; ordinary shell-script exit conventions are not interchangeable with host hook conventions.

Timeout Handling

Measure actual latency and choose proportionate local versus CI cadence. Cancellation must not leave a mutated fixture, concurrent writer or lost original artifact. When required work times out, record unassessed/failure rather than treating a background process as completed.

Error Messages

Name the check, subject, category and safe next step. Never echo secrets, credential-bearing URLs, private payloads or complete arbitrary commands merely to make a diagnostic detailed. Preserve sanitized evidence under the appropriate access policy.

Idempotency

Read-only checks should not modify the assessed subject. When a generator or test intentionally writes, use an owned isolated location, checked restoration and explicit concurrency handling. Repeated activation or external dispatch needs its own idempotency protocol, not just a repeatable hook.

Example: Custom Hook for Migration Safety

The old staged-file/table-name grep did not establish migration safety: pushed changes are often already committed, and migration-only DDL need not have ORM counterparts. Assess the exact PR/push revision range. Missing history is unassessed, not an empty change set.

Apply the real migration chain to a disposable database, inspect expected constraints/indexes and runtime privileges, test upgrade compatibility and intended data transformations, and rehearse the applicable recovery strategy. Not every destructive migration has a safe symmetric downgrade. Preserve privileged migration roles separately from application identities. A project must implement and verify these checks; this paragraph does not claim a shipped migration-safety gate.

9. Troubleshooting

Hook Does Not Fire

Check installed runtime version, event/matcher, effective settings source, executable path and mode, and whether that actor/tool mode emits the expected event. Use a harmless disposable probe. Do not broaden permissions or real-world effects to diagnose a missing hook.

Hook Fires But Does Not Block

Distinguish a script error from a host-recognized denial. Verify the documented exit/output contract and inspect whether the attempted operation actually occurred. For this PreToolUse adapter, exit 2 blocks; exit 1 does not establish denial.

Hook Is Too Slow

Measure the command with a portable timer, examine subject scope and split targeted local feedback from comprehensive CI. Do not reduce required checks silently. A background run remains pending until a matching completion receipt is available.

Stop Hook Does Not Trigger Re-Verification

That is not a capability of the supplied advisory adapter. Obtain the missing evidence explicitly and keep incomplete work labelled incomplete. Stronger wording does not turn a reminder into a deterministic completion gate.

Summary

Use one maintained adapter implementation, test the installed host integration, and report actual evidence scope. Local convenience, server enforcement, accountable approval and production acceptance are different controls. Keep all four visible.