You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
guardrails: pre-commit content scan takes 23-55 s on merge commits #10591
Likely cause (UNVERIFIED): on a merge commit the step scans the content of the whole merge diff from main, not only the branch's own changes, so its cost grows with how far main moved.
Done when:
A merge commit's content scan covers only content the commit introduces relative to its first parent's branch side (or another bounded set the security owner accepts), with no change to what a non-merge commit scans.
A test pins that a merge of a large main diff scans no more than the branch's own changes.
The scan still blocks a secret introduced by the merge's conflict resolution.
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Small
Complexity: Medium
Uncertainty: Known
Flags: none
Blocked by: none
Decision recorded. The owner chose option (a) on 2026-10-29 ("Sure, go with recommendations"): make the scan faster without narrowing it, then measure. The pre-commit content scan keeps reading every line it reads today. No line, file or merge parent is skipped.
[quote truncated; the full decision is at the link]
This decision settles the open questions. The pre-commit content scan is to do less per-file work inside guardrails' pre-commit-content.sh and read every line it reads today. Tests should cover identical findings and secret blocking. The before-and-after timing on the owner's host is a post-merge check, not lane work.
Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed - [ ] criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).
melodic-automation-lanes commented on Oct 28, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: On a merge commit, the pre-commit content scan judges only content absent from both parents (conflict resolutions and manual edits), skipping content that arrives unchanged from main, while non-merge commits scan as before.
Which bounded set does the security owner accept for a merge commit's scan? The issue says relative to the first parent's branch side, but the staged diff against HEAD (the first parent) already is main's incoming changes, so that wording does not describe a smaller set.
RECOMMENDED: Scan only lines present in neither parent (the git diff --cc set: conflict resolutions and manual edits) and skip content identical to MERGE_HEAD. Basis: plugins/guardrails/lib/git-hooks/check-lib.sh:270-272 (checks_diff runs git diff --cached, which on a merge is against HEAD, so it holds all of main's incoming diff)
Should the merge handling go in the shared checks_diff in check-lib.sh or only in pre-commit-content.sh? checks_files and checks_changed_lines both go through checks_diff, and other checks share the library.
RECOMMENDED: Add it in check-lib.sh for pre-commit mode only, detected via MERGE_HEAD, and keep worktree and pre-push behavior unchanged. Basis: plugins/guardrails/lib/git-hooks/check-lib.sh:356-398
The cause is unverified. Should the fix be confirmed by measurement before changing the scan, since the per-file loop spawns several git processes per file and could be slow for reasons other than the diff size?
RECOMMENDED: Yes: reproduce with a merge of a large main diff and count files scanned, then bound the set. Per-file cost is at plugins/guardrails/git-hooks/pre-commit-content.sh:135-185. Basis: plugins/guardrails/git-hooks/pre-commit-content.sh:135
Sources read
plugins/guardrails/git-hooks/lefthook-remote.yml:14: defines the guardrails-remote-pre-commit-content job running guardrails-pre-commit-content.sh
plugins/guardrails/git-hooks/pre-commit-content.sh:111-185: scans every file from checks_files, using ranges from checks_changed_lines
plugins/guardrails/lib/git-hooks/check-lib.sh:270-272: pre-commit mode diffs git diff --cached, with no merge handling
plugins/guardrails/lib/git-hooks/check-lib.sh:356-398: checks_files and checks_changed_lines both derive from checks_diff
plugins/guardrails/git-hooks/pre-commit-content.test.sh: has no merge-commit test (no match for merge)
To answer, post a comment that starts with this line:
**Decision recorded.** <answer>
Automated refinement by the lanes App. Answer with a comment whose first line is **Decision recorded.**.
Re-triage. No owner stop applies to the fix as bounded here. The scan keeps covering every line that is not already public. The open questions are decided below on origin/main 1ac46f037a.
Which bounded set is scanned on a merge commit?
Decided:
When MERGE_HEAD equals, or is an ancestor of, the remote-tracking tip of the default branch (origin/main), skip lines identical to the MERGE_HEAD side. That content is already public on main, so scanning it again at commit time cannot stop its exposure.
In every other merge (a local or unpushed branch), scan as today.
Lines present in neither parent are always scanned, since conflict resolutions and manual edits are new content.
Basis: plugins/guardrails/lib/git-hooks/check-lib.sh:270-272 (pre-commit mode runs git diff --cached against HEAD, so a merge carries all of main's incoming diff). A skip for any MERGE_HEAD would drop never-scanned local content, a loosening this record does not make.
Where does the merge handling live?
Decided: in check-lib.sh, for pre-commit mode only, detected via MERGE_HEAD. Worktree and pre-push behavior stay unchanged. Basis: check-lib.sh:356-398 (checks_files and checks_changed_lines both derive from checks_diff).
Is the cause verified first?
Decided: yes. Reproduce with a merge of a large main diff, record the file count and wall time, and also cut the per-file process cost (pre-commit-content.sh:135-185 runs several git processes per file) if that alone gets most of the time back. Basis: the issue's cause is marked UNVERIFIED.
Acceptance criteria:
The PR records the measured wall time and scanned-file count of the pre-commit content step on a merge of a large origin/main diff, before and after.
On a merge whose MERGE_HEAD is at or behind origin/main, the step skips lines identical to the MERGE_HEAD side, and a test pins that it scans no more than the branch's own changes plus the resolution.
A test shows that a secret introduced by the merge's conflict resolution still blocks the commit.
A test shows that on a merge of a local branch not contained in origin/main, a secret that the branch carries still blocks the commit.
A non-merge commit scans the same lines as before, and the existing pre-commit-content.test.sh cases pass unchanged.
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:
Which set does the pre-commit content scan cover on a merge commit, and where does that handling live? The body's first Done-when bullet reads 'content the commit introduces relative to its first parent's branch side (or another bounded set the security owner accepts)'. On a merge of main into a feature branch, the staged diff against HEAD (the first parent) is exactly main's incoming content (plugins/guardrails/lib/git-hooks/check-lib.sh:270-272), so that wording names no smaller set. That leaves the scan's set to the security owner. The 2026-10-29 re-triage comment answers this: skip lines identical to the MERGE_HEAD side only when MERGE_HEAD is at or behind origin/main, always scan lines present in neither parent, and put it in check-lib.sh for pre-commit mode only. That comment is not a 'Decision recorded.' comment, though, and this lane builds a loosening of a secret scanner only on a recorded decision. Please re-post those answers as a comment whose first line is 'Decision recorded.'. Please also confirm two things in it. (a) The handling goes in the canonical lib/git-hooks/check-lib.sh, whose generated copies ship in 10 plugins (actionlint, bash-format, biome-format, go-format, guardrails, instruction-placement, markdown-format, powershell-format, ruff-format, typos-format), so every one of their pre-commit checks changes on a merge and each needs a changelog fragment. Or the handling goes in guardrails' pre-commit-content.sh only. (b) Whether this lane may leave the before-and-after wall-time and file-count measurement to a host run. It cannot run git or the hook here, so it cannot take that measurement.
What the run says it did not do:
Implementation, tests and the merge measurement were not started: they wait on a recorded decision about the scan set and the scope.
tidy, review, verify and checks steps skipped: there is no change to tidy, review or verify.
The newest comment from a listed maintainer, #10591 (comment), does not start with **Decision recorded.**, so the lane did not count it as an answer. If it answers the question, post the answer again in a new comment whose first line is:
Decision recorded. The owner chose option (a) on 2026-10-29 ("Sure, go with recommendations"): make the scan faster without narrowing it, then measure. The pre-commit content scan keeps reading every line it reads today. No line, file or merge parent is skipped.
Basis:
This scan is the only secret check before a push. Pre-push runs only reference-verify (lefthook-remote.yml:17-21), and CI's gitleaks runs after the push, so on this public repository a miss here is already published.
Owner standing direction §3: "Never weaken a high/critical security requirement or boundary to gain throughput."
The cost is per-file process overhead. The loop in plugins/guardrails/git-hooks/pre-commit-content.sh:134-184 starts several processes for every staged file: checks_file_text, added_only, secrets::scan_text, secret_findings, hpp::scan_text and an awk.
A merge-commit skip was considered and declined. Review found two separate leak paths in it: a stale local main, and a private-key header already on main with a new key body written under it.
The scan of staged content does less work per file, for example by reading and scanning all text files in one batch instead of starting processes per file. Every line it reads today is still read. (body, amended: owner option (a); the change stays inside guardrails' pre-commit-content.sh and leaves the shared check-lib.sh copies alone)
A test shows the findings are the same before and after on a fixture set covering secret lines, machine paths, a NUL-byte file and a quoted file name. (body, amended: owner option (a))
The scan still blocks a commit whose staged text holds a secret, with or without a merge in progress. (body, unchanged)
Post-merge check (not lane work)
On the owner's host, time the hook on a merge commit like the ones in the body (23-55 s), before and after the change, three runs each, and post the numbers on this issue. If the scan is still slow afterwards, a skip goes back to the owner as a new question.
Activity
github-actions commentedon Oct 28, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Small
Complexity: Medium
Uncertainty: Known
Flags: none
Blocked by: none
#10591 (comment)
This decision settles the open questions. The pre-commit content scan is to do less per-file work inside guardrails'
pre-commit-content.shand read every line it reads today. Tests should cover identical findings and secret blocking. The before-and-after timing on the owner's host is a post-merge check, not lane work.Automated triage, applied by the lanes App.
melodic-automation-lanes commentedon Oct 28, 2026
Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed
- [ ]criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).melodic-automation-lanes commentedon Oct 28, 2026
The latest triage verdict holds nothing: the issue is at
status: ready.melodic-automation-lanes commentedon Oct 28, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: On a merge commit, the pre-commit content scan judges only content absent from both parents (conflict resolutions and manual edits), skipping content that arrives unchanged from main, while non-merge commits scan as before.
Related work
Open questions
RECOMMENDED: Scan only lines present in neither parent (the git diff --cc set: conflict resolutions and manual edits) and skip content identical to MERGE_HEAD. Basis: plugins/guardrails/lib/git-hooks/check-lib.sh:270-272 (checks_diff runs git diff --cached, which on a merge is against HEAD, so it holds all of main's incoming diff)
RECOMMENDED: Add it in check-lib.sh for pre-commit mode only, detected via MERGE_HEAD, and keep worktree and pre-push behavior unchanged. Basis: plugins/guardrails/lib/git-hooks/check-lib.sh:356-398
RECOMMENDED: Yes: reproduce with a merge of a large main diff and count files scanned, then bound the set. Per-file cost is at plugins/guardrails/git-hooks/pre-commit-content.sh:135-185. Basis: plugins/guardrails/git-hooks/pre-commit-content.sh:135
Sources read
To answer, post a comment that starts with this line:
Automated refinement by the lanes App. Answer with a comment whose first line is
**Decision recorded.**.kyle-sexton commentedon Oct 29, 2026
Re-triage. No owner stop applies to the fix as bounded here. The scan keeps covering every line that is not already public. The open questions are decided below on origin/main
1ac46f037a.Which bounded set is scanned on a merge commit?
Decided:
MERGE_HEADequals, or is an ancestor of, the remote-tracking tip of the default branch (origin/main), skip lines identical to theMERGE_HEADside. That content is already public on main, so scanning it again at commit time cannot stop its exposure.Basis:
plugins/guardrails/lib/git-hooks/check-lib.sh:270-272(pre-commit mode runsgit diff --cachedagainst HEAD, so a merge carries all of main's incoming diff). A skip for anyMERGE_HEADwould drop never-scanned local content, a loosening this record does not make.Where does the merge handling live?
Decided: in
check-lib.sh, for pre-commit mode only, detected viaMERGE_HEAD. Worktree and pre-push behavior stay unchanged. Basis:check-lib.sh:356-398(checks_filesandchecks_changed_linesboth derive fromchecks_diff).Is the cause verified first?
Decided: yes. Reproduce with a merge of a large main diff, record the file count and wall time, and also cut the per-file process cost (
pre-commit-content.sh:135-185runs severalgitprocesses per file) if that alone gets most of the time back. Basis: the issue's cause is marked UNVERIFIED.Acceptance criteria:
origin/maindiff, before and after.MERGE_HEADis at or behindorigin/main, the step skips lines identical to theMERGE_HEADside, and a test pins that it scans no more than the branch's own changes plus the resolution.origin/main, a secret that the branch carries still blocks the commit.pre-commit-content.test.shcases pass unchanged.1 remaining item
melodic-automation-lanes commentedon Oct 29, 2026
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:
What the run says it did not do:
The newest comment from a listed maintainer, #10591 (comment), does not start with
**Decision recorded.**, so the lane did not count it as an answer. If it answers the question, post the answer again in a new comment whose first line is:Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/40844931255
To retry, answer the question in a comment whose first line is
**Decision recorded.**, then removeneeds-humanand addagent-readyagain.kyle-sexton commentedon Oct 30, 2026
Decision recorded. The owner chose option (a) on 2026-10-29 ("Sure, go with recommendations"): make the scan faster without narrowing it, then measure. The pre-commit content scan keeps reading every line it reads today. No line, file or merge parent is skipped.
Basis:
This scan is the only secret check before a push. Pre-push runs only reference-verify (
lefthook-remote.yml:17-21), and CI's gitleaks runs after the push, so on this public repository a miss here is already published.Owner standing direction §3: "Never weaken a high/critical security requirement or boundary to gain throughput."
The cost is per-file process overhead. The loop in
plugins/guardrails/git-hooks/pre-commit-content.sh:134-184starts several processes for every staged file:checks_file_text,added_only,secrets::scan_text,secret_findings,hpp::scan_textand anawk.A merge-commit skip was considered and declined. Review found two separate leak paths in it: a stale local main, and a private-key header already on main with a new key body written under it.
The scan of staged content does less work per file, for example by reading and scanning all text files in one batch instead of starting processes per file. Every line it reads today is still read. (body, amended: owner option (a); the change stays inside guardrails'
pre-commit-content.shand leaves the sharedcheck-lib.shcopies alone)A test shows the findings are the same before and after on a fixture set covering secret lines, machine paths, a NUL-byte file and a quoted file name. (body, amended: owner option (a))
The scan still blocks a commit whose staged text holds a secret, with or without a merge in progress. (body, unchanged)
Post-merge check (not lane work)
On the owner's host, time the hook on a merge commit like the ones in the body (23-55 s), before and after the change, three runs each, and post the numbers on this issue. If the scan is still slow afterwards, a skip goes back to the owner as a new question.