Skip to content

guardrails: shared install-git-hooks copies fail consumer CI (mode 100644, linted vendored scripts) #9426

Closed

Description

@kyle-sexton

Problem

/guardrails:setup apply install-git-hooks scope=shared (guardrails 0.55.4, lib/git-hooks/install.mjs copy --scope shared) produces copies that fail a consuming repository's own CI, so every repository adopting the shared entries needs the same manual fix-up. Seen on melodic-software/standards#852:

  1. Executable bit. The installer stages the copies as mode 100644. Seven of them carry a shebang (check-lib.sh, cli-flag-verify.sh, resolve-convention-pattern.sh, run-guards.sh, skill-reference-verify.sh, stale-path-verify.sh, verify-cli-flag.sh), and standards' check-repo-hygiene rejects that: has a shebang but git index mode is 100644; run: git update-index --chmod=+x.
  2. Lint of vendored copies. The repository's ShellCheck lanes (lefthook pre-commit and the ci-workflows shellcheck action) lint .githooks/guardrails/ and fail on the copies (SC2218 and others under that repo's .shellcheckrc). The pilot added .githooks/guardrails/** to the pre-commit exclude and .githooks/guardrails/ to the action's exclude; nothing in plan tells the operator to do this.
  3. Cost of the first commit. guardrails-pre-commit-content scanned the ~10.7k vendored lines for 70 s on the install commit (0.15 s on a normal commit afterwards).

Expected

  • copy stages shebang files as 100755 (or plan returns a decision to).
  • plan returns a decision naming the repository's linters that will see .githooks/guardrails/ (or the copies pass ShellCheck under a default rc), so the exclusion is part of the install, not discovered in CI.
  • pre-commit-content skips its own plugin-owned copies.

Related

Rolling the shared entries out across the melodic-software repositories; each would hit 1 and 2.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bp5vTm7VLLUuUBumknREY5

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 23, 2026
  2. github-actions commented on Oct 23, 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. (agent; not an owner decision) #12402 builds findings 1 and 8, which the 2026-10-31 record left unbuilt: a shared install now stages and writes every shebang copy as 100755/0755, and a rerun repairs a wrong exec bit. Basis: copy still writes copies for plain and husky shared installs (plugins/guardrails/lib/git-hooks/install.mjs:919-928 at 13617b6; plugins/guardrails/git-hooks/pre-commit-content.test.sh:363 installs plain shared), and the fix is internal to the installer with no new mechanism. The PR uses Refs:; the five acceptance criteria of the 2026-10-31 record and finding 2 stay open here.

    #9426 (comment)

    I read this as a defect in guardrails' shared git-hooks install. The latest recorded decision settles the open questions: the exec-bit fix is in progress in #12402, and the remaining acceptance criteria and finding 2 (plan naming linters) are still open work. No person's decision is pending. I found no duplicate among open issues.

    Automated triage, applied by the lanes App.

  3. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 23, 2026
  4. added
    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.
    status: needs-decisionAwaiting a human or maintainer judgment call.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 23, 2026
  5. melodic-automation-lanes commented on Oct 23, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane: its done-when asks for what an unattended lane run cannot perform: item 14 (a comment instead of a file change: "A comment on this issue records findings 1-3, 8-11 and the machine-path comme..."); recommended split: keep the other items here as the admissible issue and move these to a child issue for the host queue (host-run); it carries host-run. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).

  6. kyle-sexton commented on Oct 23, 2026

    @kyle-sexton
    MemberAuthor

    A third failure from the same copies: the repo's check-machine-paths CI step flags the example paths in comments of .githooks/guardrails/machine-path-patterns.sh:41 and hardcoded-path-patterns.sh:37 (they self-match, like a pattern source). The pilot added ':(exclude).githooks/guardrails/**' to that step in standards#852. plan should name this exclusion too, or the comments should use placeholders.

  7. kyle-sexton commented on Oct 23, 2026

    @kyle-sexton
    MemberAuthor

    Two more from the standards#852 pilot:

    1. reference-verify.sh silently skips files over the per-argument limit. It builds each payload with jq -n --arg c "$text" (lines ~123 and ~131 of the 0.55.4 copy). A file whose content passes the Linux per-argument limit (128 KiB) fails with Argument list too long before jq starts, the payload is empty, no finding is produced, and the hook exits 0, so all three reference checks are skipped for that file. Found by the Codex review on the pilot, reproduced on the 238,555-byte hook-utils.sh. Fix: feed the content through stdin or a temp file (jq --rawfile), in both the Write and Edit branches, and fail on a payload-construction error.
    2. No repo-side exemption for a pattern source. In the observation replay of standards' last 100 commits, the only pre-commit-content finding was components/cloud-environment/pages-publish (a scanner whose pattern bodies document /home/<x> and /Users/<x> forms): it self-matches, as the plugin's own pattern libraries do (path_exempt lists them). path_exempt is hard-coded in the copy, so a consumer cannot add its own pattern source without editing a copy. A tracked exemption list read from the repo would cover it.
  8. kyle-sexton commented on Oct 23, 2026

    @kyle-sexton
    MemberAuthor

    More from the rollout in this repository (#9462), guardrails 0.55.4:

    1. cli-flag-verify reads flags inside a quoted prompt. The reference-verify replay of origin/main~200..origin/main reported claude on-demand --scheduled (not in 'claude on-demand --help') for plugins/machine-health/CHANGELOG.md and plugins/machine-health/skills/setup/SKILL.md. The text is claude -p "/machine-health:audit on-demand --scheduled": --scheduled is a skill argument inside the -p prompt string, not a CLI flag. The finding's line number is the file's first changed line (first_line in reference-verify.sh), not the line holding the flag (6 and 2 here, against 29 and 345), so the reader has to search for it.
    2. Item 4 seen live on the install push. The push that adds the copies sends .githooks/guardrails/hook-utils.sh (238,555 bytes) whole, as a new file; reference-verify.sh printed line 125: .../jq: Argument list too long and the job still passed. Every repository's install push skips that file this way.
    3. On-disk mode. copy writes seven shebang copies 0644 on disk as well as in the index; after git update-index --chmod=+x they show as mode changes in git status until chmod +x, and scripts/check-affected.sh here skips every check while they do.
    4. Consumer lint mirrors. This repository's local scripts/check-affected.sh ShellCheck list does not read the workflow's exclude:, so the pre-push job blocks any push that changes the copies; and scripts/affected-tests.sh reports the copies as UNMAPPED. Both are repo-side follow-ups here, noted for the plan decision that names linters (item 2).
  9. kyle-sexton commented on Oct 23, 2026

    @kyle-sexton
    MemberAuthor

    Item 2 follow-up from the dotfiles rollout (dotfiles#1293): a consumer that runs ShellCheck through the ci-workflows pr-run-checks.yml reusable cannot exclude the copies. The reusable's Lint shell scripts step passes no inputs to the shellcheck composite (at ab83b01, v0.39.3, and on ci-workflows main as of 2026-10-23), so .githooks/guardrails/ is linted and fails on SC2218 (run-guards.sh:569, error level), SC2329 (run-guards.sh:744) and SC1091. The repo-side workaround the pilot used (the composite's own exclude:) only reaches a direct call of the composite. Fix options: make the copies ShellCheck-clean under a strict rc (removes the need for any exclusion), or add a shellcheck-exclude input to the reusable.

  10. 49 remaining items

  11. added
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Nov 2, 2026
  12. kyle-sexton commented on Nov 2, 2026

    @kyle-sexton
    MemberAuthor

    Host run. Fixed the file modes install.mjs copy --scope shared gives its copies (findings 1 and 8), at the canonical lib/git-hooks/install.mjs, and regenerated the ten plugin copies.

    What ran:

    • Edited lib/git-hooks/install.mjs: a support copy (the check lib or an asset) that starts with #! is written 0755 and staged 100755. Any other copy is written 0644 and staged 100644 (--chmod=-x). When a copy's exec bit is wrong, a rerun rewrites the copy and reports refreshed. Stable copies made by configHook follow the same rule.
    • scripts/sync-shared-copies.sh regenerated the copies, and --check passes: all 567 copies match.
    • New contract test in lib/git-hooks/install.test.mjs. It fails on the old installer and passes on the new one. The whole file passes: 73 pass, 4 skipped for git below 2.54.
    • Scratch-repo probe of the real guardrails plugin (all four entries, shared, plain): 14 shebang files are all 100755 and executable on disk, every other file is 100644, 0 violations. The seven files the issue named are among them.
    • scripts/check-affected.sh --tests on the committed head: 13 passed, 3 skipped.

    PR: #12402 (Refs: #9426).

    Remaining on this issue: the five acceptance criteria in the 2026-10-31 decision, and finding 2 (plan naming the linters that will see the copies). No consumer repository needs a change for this fix.

    Target labels: add none; remove host-run.

  13. removed
    host-runNeeds an unattended run on a host; the host runner takes it, not a person or the issue lane.
    on Nov 2, 2026
  14. self-assigned this
    on Nov 3, 2026
  15. kyle-sexton commented on Nov 3, 2026

    @kyle-sexton
    MemberAuthor
    No description provided.
  16. kyle-sexton commented on Nov 3, 2026

    @kyle-sexton
    MemberAuthor
    No description provided.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

priority: mediumReal value, no hard deadline; normal backlog flow.status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

Type

Fields

Work status

confirmed

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions