fix: resilient merge status parsing for PostToolUse hooks #201
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!201
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "189-fix-merge-hook-false-alarm"
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
Three PostToolUse hooks firing on
mcp__forgejo__merge_approved_prused inline two-stage jq parsing that broke when Claude Code changed the PostToolUse stdin JSON shape. This caused false "merge not successful" messages on successful squash merges. The fix extracts a shared_parse_merged_status()function that handles all known JSON shapes.Changes
hooks/forgejo-helper.sh-- Added_parse_merged_status()function that tries 5 JSON shapes: direct field, stringified result, MCP content array, string response, and grep fallback. Includes debug capture to/tmp/hook-debug-merge.json.hooks/remind-update-docs.sh-- Replaced inline parsing with shared_parse_merged_status()callhooks/post-mcp-merge-rebase.sh-- Replaced inline parsing with shared_parse_merged_status()call, removed redundantforgejo-helper.shdouble-sourcehooks/board-item-on-merge.sh-- Replaced inline parsing with shared_parse_merged_status()calltests/test_parse_merged_status.sh-- 13 unit tests covering all 5 shapes + edge cases (error responses, empty/missing tool_response, non-JSON content)Test Plan
bash tests/test_parse_merged_status.sh/tmp/hook-debug-merge.jsonwill reveal actual shape)Review Checklist
Related Notes
project-pal-e-agencyRelated
Closes #189
PR #201 Review
DOMAIN REVIEW
Tech stack: Bash / shell hooks (PostToolUse hooks for Claude Code), jq JSON parsing, unit tests in bash.
Correctness
The core change is sound. Three hooks (
remind-update-docs.sh,post-mcp-merge-rebase.sh,board-item-on-merge.sh) each had identical inline two-stage jq parsing for detecting merge success. When Claude Code changed the PostToolUse stdin JSON shape, all three broke identically. Extracting_parse_merged_status()intoforgejo-helper.shis the correct DRY fix.The function tries 5 shapes in priority order with early return on match, falling through to a grep-based last resort and finally returning "false". This is defensively coded.
Observations on
_parse_merged_status()implementation:Debug capture to
/tmp/hook-debug-merge.json-- This is a reasonable diagnostic aid for future regressions. The2>/dev/null || trueguard is correct. However, note that all three hooks fire on the samemerge_approved_prevent, so they will race to write this file. The last one wins, but since they all receive the same stdin content, this is a non-issue in practice.Shape 4 has a subtle interaction with Shape 1: When
.tool_responseis an object (not a string),jq -r '.tool_response // empty'will output the object as a JSON string, then the secondjq -r '.merged // empty'will parse it and find.merged. This means Shape 4 could match cases that Shape 1 already handles. Not a bug (Shape 1 returns first), but worth noting that Shape 4 is not purely for "string tool_response" -- it would also catch any object with amergedfield at the top level oftool_response. The comment says "string" but it works on objects too.Shape 5 grep fallback --
grep -q '"merged"[[:space:]]*:[[:space:]]*true'on the raw.tool_responseoutput is a reasonable last resort. The risk of false-positive matching on an unrelated"merged": trueburied in some nested structure is extremely low for this particular API response shape.Source ordering in
post-mcp-merge-rebase.sh: The PR correctly moves thesource forgejo-helper.shto the top (afterINPUT=$(cat)) and removes the duplicate source at line 64-65. This is clean -- the helper is sourced once, used twice (for_parse_merged_statusand later forremove_worktree_for_branch).Source addition in
remind-update-docs.sh: This hook did not previously sourceforgejo-helper.sh. The PR adds it purely to access_parse_merged_status(). Sinceforgejo-helper.shis purely function definitions with no side effects on source, this is safe. TheHOOK_DIRvariable is also new and correctly defined.BLOCKERS
None.
/tmp/hook-debug-merge.jsoncontains only the PostToolUse JSON payload (tool name, tool input, tool response), not credentials.NITS
Missing Shape 4 false test: The test file has explicit false-case tests for Shapes 1, 2, and 3, but not for Shape 4 (string tool_response with
"merged": false). Adding one would make the test suite more symmetric. Non-blocking since the code path does fall through correctly to return "false" when.mergedis not "true".Comment accuracy on Shape 4: The doc comment says "
.tool_response | strings | fromjson | .merged" but the actual implementation uses.tool_response // empty(notstrings). The filter// emptyapplied to a non-string.tool_responsewill output the object as JSON, which then gets parsed by the secondjq. The doc comment describes intended behavior; the implementation is slightly broader. Minor clarity gap.No
set -euo pipefailin_parse_merged_status: The function is designed fail-open (all2>/dev/null, always returns 0), so this is by design. Just noting that callers must be aware it never returns non-zero.SOP COMPLIANCE
189-fix-merge-hook-false-alarm-> issue #189)project-pal-e-agencyplan slugCloses #189present in PR bodyPROCESS OBSERVATIONS
/tmp/hook-debug-merge.jsondebug capture will make the next regression significantly faster to diagnose.VERDICT: APPROVED