Skip to content

feat(git-hooks): install hook entries as git config-based hooks under an includeIf, from git 2.54 #9775

Description

@kyle-sexton

Parent

Refs #5948

Depends on

Outcome

/guardrails:setup offers a config-hook install: it writes a user-scope [hook "<name>"] block (event
commit-msg) under an includeIf the operator chooses, pointing at a stable copy of the hook, instead of
copying into .git/hooks. It covers every clone and linked worktree under the tree and runs beside lefthook,
husky, pre-commit or core.hooksPath managers. Bypass never locks out: a one-off environment variable and
git -c hook.<name>.enabled=false, both documented.

Decisions

  • Step B (owner, 2026-10-13): yes, with the git 2.54 floor; tests gate on the git version, and the docs say
    that gits below 2.54 ignore hook.* silently.
  • Failure text never suggests --no-verify.

Key interfaces

  • lib/git-hooks/install.mjs and copies, plugins/guardrails/skills/setup/ (context/install-commit-msg.md),
    lib/git-hooks/git-hook-decisions.md.

Done when

  • An install test, run twice against a temp HOME and a test repo under a temp directory, leaves one [hook "<name>"] block, and git hook list commit-msg in the repo shows it (skipped with a visible SKIP line when git --version is below 2.54).
  • In a lefthook-managed test repo, a commit runs both lefthook's hook and the config hook (same version gate).
  • git -c hook.<name>.enabled=false commit in the test repo skips the hook.
  • grep -n "2.54" plugins/guardrails/skills/setup/context/install-commit-msg.md prints the floor and below-floor behavior.
  • scripts/sync-shared-copies.sh --check exits 0.
  • scripts/check-changelog-fragments.sh --check-required origin/main exits 0.
  • The PR body carries Hold: a human merges this.

Activity

  1. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 24, 2026
  2. melodic-automation-lanes commented on Oct 24, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane yet: blocker #9774 is not closed as completed by a listed human or a merge.

  3. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 24, 2026
  4. melodic-automation-lanes commented on Oct 25, 2026

    @melodic-automation-lanes
    Contributor

    The work-resolve-issue lane did not start this issue: it needs a person to make it clearer first.

    Why, in the run's own words:

    The issue requires an environment-variable bypass that the repository's own rules forbid, and it does not say which variable. The Outcome asks for 'a one-off environment variable and git -c hook.&lt;name&gt;.enabled=false, both documented', but no recorded decision covers it. The 2026-10-13 Step B decision covers only the git 2.54 floor, and #5948's comments never choose a variable. The repository rejects this kind of bypass in two places: docs/conventions/check-placement/README.md:161-175 says checks.skipmissing is 'The only way to skip a check' and that there is 'no environment variable, no --no-verify', and docs/adr/0072-place-each-check-by-coverage-cost-and-model-facing-output.md:96-98 rejects 'An environment-variable skip' because every child process inherits it. Please answer: (1) Should the config-hook install have an environment-variable bypass at all? If yes, name the variable and say whether ADR 0072 and the check-placement skip rule get amended in this PR or a separate one. If no, drop it and keep git -c hook.&lt;name&gt;.enabled=false as the only documented bypass, and say whether guardrails' block-no-verify guard should allow that key the way it allows checks.skipmissing. (2) Neither the issue nor #5948 picks two things the install writes into user config: where the 'stable copy of the hook' lives (for example a directory under the user's home or XDG config) and the \[hook "&lt;name&gt;"\] friendly name. Please pick both, or say the implementer may choose them.

    What the run says it did not do:

    • Nothing was built: the install.mjs config-hook action, the context/install-commit-msg.md docs, the version-gated tests and the changelog fragment all wait on the bypass, copy-location and friendly-name decisions above

    Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/40208812920

    To retry, answer the question here, remove needs-human and add agent-ready again.

  5. kyle-sexton commented on Oct 25, 2026

    @kyle-sexton
    MemberAuthor

    Decision recorded.

    Owner's answer, given in the GitHub Support session on 2026-10-25 at about 20:0xZ: "go with whatever the facts/data/metrics + /discovery:research says and ensure we're aligned with SOLID, DRY/SSOT principles". It answers the two questions in the lane's pushback (run 40208812920), which the hub put to the owner with these recommendations.

    1. Should the config-hook install have an environment-variable bypass, or a git -c hook.<name>.enabled=false bypass? No to both. checks.skipmissing stays the only skip.
      • The repository already decides this. check-placement README lines 161-175 say: "The only way to skip a check is one git config key ... no environment variable, no --no-verify, no core.hooksPath redirect. Guardrails allows exactly this key and keeps blocking the other two."
      • ADR 0072 lines 96-98 reject an environment-variable skip because every process inherits it.
      • So ADR 0072, the check-placement skip rule and guardrails' block-no-verify guard stay unchanged. The Outcome's "a one-off environment variable and git -c hook.<name>.enabled=false, both documented" is replaced by checks.skipmissing.
    2. Where does the stable hook copy live, and what is the [hook "<name>"] friendly name? The implementer chooses both. The review lanes judge the choice.

    Acceptance criteria that replace or add to the issue's own:

    • The config-hook install adds no environment-variable bypass and no hook.<name>.enabled=false bypass. The install's docs name checks.skipmissing as the only skip.
    • The criterion "git -c hook.<name>.enabled=false commit in the test repo skips the hook" is dropped. In its place, a test shows git -c checks.skipmissing=<tool> commit skips a check whose tool is absent and still runs it when the tool is installed.
    • ADR 0072, docs/conventions/check-placement/README.md and guardrails' block-no-verify guard are unchanged by this work.
    • The stable hook copy's location and the [hook "<name>"] friendly name are written in the install's docs.
  6. removed
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 26, 2026
  7. kyle-sexton commented on Oct 26, 2026

    @kyle-sexton
    MemberAuthor

    Released for re-triage under the owner's 2026-10-26 rule: a person is involved only for "1. A change in original intent/vision 2. Something comes up during implementation ... that alters intent/vision or the primary design 3. If there is a HIGH/CRITICAL security issue". None applies here, so the hold label came off and intake triage runs again now.

    To hold this issue again, add needs-human (or status: needs-decision) and name which of the three applies.

  8. github-actions commented on Oct 26, 2026

    @github-actions
    Contributor

    Proposed work class: work-class: scoped
    Basis: automated intake triage; the lanes App applied this class.

    Size: Medium
    Complexity: Medium
    Uncertainty: Known
    Flags: none
    Blocked by: none

    Decision recorded.

    Owner's answer, given in the GitHub Support session on 2026-10-25 at about 20:0xZ: "go with whatever the facts/data/metrics + /discovery:research says and ensure we're aligned with SOLID, DRY/SSOT principles". It answers the two questions in the lane's pushback (run 40208812920), which the hub put to the owner with these recommendations.

    1. Should the config-hook install have an environment-variable bypass, or a git -c hook.<name>.enabled=false bypass? No to both. checks.skipmissing stays the only skip.
      [quote truncated; the full decision is at the link]

    #9775 (comment)

    This decision settles the open questions: no environment-variable or enabled=false bypass, checks.skipmissing is the only skip, and the implementer picks the stable copy location and the hook friendly name. The issue is a bounded feature in the git-hooks install and the guardrails setup docs. #9774 isn't in the open-issue list, so I treated it as no longer blocking. Please confirm that.

    Automated triage, applied by the lanes App.

  9. kyle-sexton commented on Oct 26, 2026

    @kyle-sexton
    MemberAuthor

    Released for re-triage under the owner's 2026-10-26 rule: a person is involved only for "1. A change in original intent/vision 2. Something comes up during implementation ... that alters intent/vision or the primary design 3. If there is a HIGH/CRITICAL security issue". None applies here, so the hold label came off and intake triage runs again now.

    To hold this issue again, add needs-human (or status: needs-decision) and name which of the three applies.

  10. github-actions commented on Oct 26, 2026

    @github-actions
    Contributor

    Intake triage did not triage this issue after 2 dispatches, so the sweep no longer dispatches it and labeled it needs-triage for a maintainer. Triage it by hand, and read the runs for why intake did not:

  11. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageNot yet classified. Floor until a type and one priority tier are set.priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Fields

    No fields configured for Feature.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions