feat: pre-merge infra validation hook #123
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/claude-custom!123
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "122-pre-merge-infra-validation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
PreToolUse hook that blocks merging infrastructure PRs without validation evidence. Fires on
mcp__forgejo__merge_approved_pralongside the existingblock-mcp-merge.shgate. Non-infra repos pass through unaffected.Changes
hooks/check-infra-validation.sh(new) -- PreToolUse hook with per-repo validation logic:tofu plan/Plan:evidencekubectl kustomize/kustomize buildevidencesettings.json-- registered hook under PreToolUsemcp__forgejo__merge_approved_prmatcherTest Plan
bash -n hooks/check-infra-validation.sh-- syntax check passesReview Checklist
chmod +x)Related
plan-pal-e-agencyPhase 17aPR #123 Review
DOMAIN REVIEW
Tech stack: Bash shell hook + JSON (settings.json). Domain: Claude Code hook protocol, Forgejo API integration, shell scripting.
Hook architecture: The hook correctly sources the shared
forgejo-helper.shfor credential loading, follows the establishedPreToolUsepattern, and registers under the existingmcp__forgejo__merge_approved_prmatcher alongsideblock-mcp-merge.sh. The fail-open design (exit 0 when credentials are missing or repo is non-infra) is appropriate for a gate that should not block unrelated work.API usage: Uses
curlwith--connect-timeout 5 --max-time 10consistently, matching the pattern inforgejo-helper.sh. Pipes throughjqwith2>/dev/nullfallbacks. Error handling is solid.Shell quality:
set -eo pipefailis consistent with other hooks (check-pr-template.sh,check-note-template.sh, etc.).HOOK_DIRresolution is correct.shellcheck disable=SC1091for the dynamic source is appropriate.BLOCKERS
1.
permissionDecision: "block"is not a valid Claude Code hook value -- must be"deny"The
block()function on diff line 94 outputs:Every other blocking hook in this codebase uses
"deny":check-pr-template.sh--"deny"check-agent-spawn.sh--"deny"block-upstream.sh--"deny"block-main-commits.sh--"deny"check-ruff-before-commit.sh--"deny"block-claude-custom-main-edit.sh--"deny"The Claude Code hook protocol recognizes
"allow","deny", and"ask". The value"block"is non-standard and will likely be treated as unknown, meaning the hook will silently fail to prevent the merge. This completely defeats the purpose of the hook.Fix: Change
"block"to"deny"in theblock()function.NITS
Misleading header comment (diff line 13): The comment says "Exits 2 with explanation if validation evidence is missing" but the
block()function exits 0 (which is correct for the JSON protocol). The comment should say "Outputs deny JSON if validation evidence is missing" or similar.Overly broad evidence pattern for pal-e-services: The pattern
Plan:(diff line 137) is very generic -- any PR comment containing "Plan:" anywhere would satisfy the check. This could lead to false passes. Consider tightening to something liketofu plan|terraform plan|## tofu plan Output|Plan: \d+ to addto match actual plan output rather than prose references.Two API calls for pal-e-platform HEAD SHA: The hook fetches the PR details to get
head.sha(diff line 107), then makes a second call to get the commit status (diff line 116). The first call is also made byfetch_pr_body(). If this hook ever needs both the body and SHA for a single repo, this could be consolidated. Not an issue today since pal-e-platform only uses the SHA path.Consider
"ask"instead of"deny"for pending CI: For pal-e-platform when CI state is"pending"(diff line 126), using"ask"instead of"deny"might be more appropriate -- letting the user decide whether to wait or override. The hard deny is defensible but could block a legitimate override scenario. This is a design choice, not a bug.SOP COMPLIANCE
122-pre-merge-infra-validationreferences issue #122plan-pal-e-agencyPhase 17aforgejo-helper.shat runtime)Closes #122PROCESS OBSERVATIONS
Deployment frequency: This hook adds a pre-merge gate for three infra repos. Once the
"block"->"deny"fix is applied, this will enforce validation evidence before merge, which is a DORA-positive change (reduces change failure rate by catching missing validation).Change failure risk: Low risk once the blocker is fixed. The fail-open design means a broken hook cannot lock out merges. The hook only fires on the
mcp__forgejo__merge_approved_prtool, so it cannot affect non-merge operations.Documentation: The hook header comment is well-documented with per-repo logic explained. The SOP reference (
sop-platform-tf-changes) is included in all block messages.VERDICT: NOT APPROVED
One blocker:
permissionDecision: "block"must be changed to"deny"to match the Claude Code hook protocol. Without this fix, the hook will silently fail to prevent merges on infra repos, making it a no-op safety gate.PR #123 Re-Review
Re-review after blocker fix. Previous review found
permissionDecision: "block"should be"deny".DOMAIN REVIEW
Tech stack: Bash hooks for Claude Code PreToolUse system, Forgejo API integration, JSON/jq processing.
Hook architecture: The new
check-infra-validation.shfollows established patterns from the existing hook suite. It sourcesforgejo-helper.shfor credential management, uses consistent curl timeouts (--connect-timeout 5 --max-time 10), and outputs the standardhookSpecificOutputJSON structure matchingblock-mcp-merge.sh.Fail-open design: Correct. All error paths (missing tool_input, missing creds, non-infra repos, unreachable end) exit 0. This is the right choice -- a broken validation hook should not block all merges.
Credential handling: No hardcoded secrets. Credentials loaded via
_load_forgejo_credsfrom~/secrets/pal-e-services/forgejo.env. Authentication uses-ubasic auth consistent with all other Forgejo API hooks.Per-repo validation logic:
pal-e-platform: Fetches head SHA, checks combined commit status API. Distinguishessuccess/pending/other states with appropriate messages.pal-e-services: Searches PR body + comments for tofu plan evidence via regex.pal-e-deployments: Searches PR body + comments for kustomize build evidence via regex.settings.json: Hook correctly registered as second entry under the existing
mcp__forgejo__merge_approved_prmatcher array. Valid JSON structure.BLOCKERS
None. Previous blocker resolved:
permissionDecisionnow correctly set to"deny"(was"block"). Theblock()function at line ~89 of the new file outputs the correct value matching Claude Code's PreToolUse hook specification.NITS
Broad
Plan:pattern (line ~136 evidence regex): The pattern'tofu plan|Plan:|terraform plan|tofu.plan|## tofu plan Output'includes barePlan:which with case-insensitive grep could match unrelated text like "Test Plan:" in the PR template. Since false positives here mean allowing the merge (safe direction), this is low-risk. But tightening totofu plan|Tofu Plan|terraform plan|tofu.plan|## tofu plan Output|## Plan Outputwould reduce noise without losing real matches.fetch_pr_commentsjq filter: The filter'.[].body // ""'would produce empty strings for comments with null bodies rather than skipping them. Harmless but'[.[].body // ""] | join("\n")'would be marginally cleaner for the grep pipeline.No
set -u: The script usesset -eo pipefailbut notset -u(nounset). Existing hooks in the repo also omit-u, so this is consistent with the codebase convention, but unset variable references would silently expand to empty strings rather than erroring.SOP COMPLIANCE
122-pre-merge-infra-validationnamed after issue #122plan-pal-e-agency Phase 17aPROCESS OBSERVATIONS
This hook closes a gap in the merge safety chain -- infrastructure repos (pal-e-platform, pal-e-services, pal-e-deployments) now require validation evidence before merge. Combined with the existing
block-mcp-merge.shuser-confirmation gate, this creates a two-layer defense: (1) must have validation evidence, (2) must get explicit user approval. Good CFR (Change Failure Rate) improvement for infrastructure changes.VERDICT: APPROVED
PR #123 Review
DOMAIN REVIEW
Tech stack: Bash shell scripting (PreToolUse hook for Claude Code harness), JSON configuration (
settings.json).Hook architecture: The new
check-infra-validation.shfollows established patterns from this repo's hook library:forgejo-helper.shfor credential loading (_load_forgejo_creds)set -eo pipefailfor strict error handlingjqfor JSON parsing (both input and output)PreToolUsehook contractThree validation paths are cleanly separated by repo name:
pal-e-platform-- CI status check via Forgejo commit status API (requires green Woodpecker)pal-e-services-- grep fortofu planevidence in PR body/commentspal-e-deployments-- grep forkustomize buildevidence in PR body/commentsControl flow correctness: Each repo block either calls
exit 0(pass) orblock(which outputs deny JSON and callsexit 0). The blocks are mutually exclusive by repo name, and the trailingexit 0at the end is a safe catch-all. No fall-through risk.settings.json integration: The new hook is registered under the existing
mcp__forgejo__merge_approved_prmatcher alongsideblock-mcp-merge.sh. Execution order is correct --block-mcp-merge.shprompts for user approval ("ask" decision), thencheck-infra-validation.shvalidates evidence. Both must pass for the merge to proceed.Credential handling: Uses
_load_forgejo_credsfromforgejo-helper.sh, which sources from~/secrets/pal-e-services/forgejo.env. No hardcoded credentials. Credentials passed via-uflag to curl, consistent with all other hooks in this repo.BLOCKERS
None.
tool_inputJSON) and Forgejo API responses. No user-facing input surfaces._load_forgejo_credshelper.NITS
Unescaped dot in regex (line ~133 of the hook): The evidence pattern
'tofu plan|Plan:|terraform plan|tofu.plan|## tofu plan Output'containstofu.planwhere.is a regex wildcard matching any character. This would matchtofuxplanortofu-planin addition totofu.plan. Should betofu\.planfor a literal dot match. Non-blocking since false positives here are harmless (they would allow merges, not block them).Duplicate PR fetch in pal-e-platform path: The
pal-e-platformblock fetches the PR JSON to extracthead.sha(line ~96). If this repo ever needed evidence checking too, it would fetch the PR again viafetch_pr_body. Currently not an issue since the three paths are mutually exclusive, but afetch_pr_datahelper that caches the response would reduce future duplication. Very minor.PR body + comments API calls could be consolidated:
fetch_pr_bodyandfetch_pr_commentsmake two separate API calls. The PR body is already available from the hook'stool_input(the merge call has access to the PR context). However, since this hook receivesmerge_approved_prinput (which only has owner/repo/pr_number), the API calls are necessary. No action needed.Comments pagination:
fetch_pr_commentsuses?limit=50. For PRs with extensive discussion, this could miss evidence in later comments. Extremely unlikely to matter in practice -- infra PRs rarely have 50+ comments. Non-blocking.SOP COMPLIANCE
122-pre-merge-infra-validationreferences #122)plan-pal-e-agency Phase 17a)Closes #122)feat: pre-merge infra validation hook)chmod +xper review checklist)PROCESS OBSERVATIONS
VERDICT: APPROVED