Skip to content

fix(#382): refine gh api matcher to only block POST on /issues#384

Merged
atlas-apex merged 1 commit into
devfrom
fix/GH-382-gh-api-matcher-refinement
May 23, 2026
Merged

fix(#382): refine gh api matcher to only block POST on /issues#384
atlas-apex merged 1 commit into
devfrom
fix/GH-382-gh-api-matcher-refinement

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

  • Refines the gh api repos/ matcher in require-skill-for-issue-create.sh so it only blocks POST writes against /issues endpoints — previously any read-only gh api repos/owner/repo/contents/... GET was caught by the pure substring prefix and blocked with exit 2, which made the hook a routine nuisance for read-only API calls.
  • Surgical case-match added after $MATCHED is set: if the matched pattern starts with gh api, downgrade to exit 0 unless the command BOTH targets /issues AND has a write signal (-X POST / --method POST / -f / -F / --field / --raw-field / --input). Placement is intentional — runs only on matched patterns and only refines the gh api case, so non-issue-create false positives are eliminated without weakening the gate against actual POSTs.
  • Other tracker patterns unchangedgh issue create, linear issue create, jira issue create, asana task create keep their pure-prefix matching. The refinement targets only the one matcher that had no HTTP-method or endpoint information embedded in its prefix.
  • New regression test at .claude/hooks/tests/test_require_skill_for_issue_create_gh_api.sh covers 6 cases: GET on /contents, GET on /issues/<id>, POST on /pulls (not /issues), POST on /issues via --method, POST on /issues via field flag, and gh issue create. 6/6 PASS. Existing test_require_skill_for_issue_create.sh 18/18 still PASS — no regression on the non-gh api patterns.

Testing

  • New test file .claude/hooks/tests/test_require_skill_for_issue_create_gh_api.sh — 6/6 PASS
  • Existing test .claude/hooks/tests/test_require_skill_for_issue_create.sh — 18/18 PASS (no regression)
  • Manual repro: gh api repos/o/r/contents/README.md now exits 0 (previously exit 2)
  • Verify still blocks: gh api repos/o/r/issues -f title=x still exits 2
  • shellcheck -S warning — could not run locally (shellcheck not installed on this machine); CI will exercise it

Glossary

Term Definition
ticket.create_command_patterns Configurable list in .claude/project-config.{defaults,}.json of CLI command prefixes the hook treats as ticket-create attempts. Adopters extend it for Linear / Jira / Asana / custom trackers.
Field flag (-f / -F / --field) gh-cli flags that pass key=value pairs to an API call; their presence implies the call is a POST (gh switches from default GET when fields are supplied).
Issues endpoint API paths matching /issues (including /issues, /issues/N, /issues/N/comments etc.). The hook narrows the block to writes against THIS endpoint specifically.
Bootstrap-skill exemption Existing path that lets bootstrap-class skills (/setup, /handover, /update, /split-portfolio) bypass the gate — preserved unchanged by this PR.

Closes #382

The pattern `gh api repos/` in ticket.create_command_patterns was
a pure substring prefix with no HTTP-method or endpoint refinement,
so any read-only `gh api repos/owner/repo/contents/...` GET was
blocked as if it were a ticket-create POST.

Add a case-match for the `gh api` family after $MATCHED is set: if
the command does not BOTH target /issues AND carry a write signal
(-X POST / --method POST / -f / -F / --field / --raw-field /
--input), downgrade the match to exit 0 (no-op).

Other tracker CLIs (gh issue create, linear issue create, jira
issue create, asana task create) are unaffected — their patterns
stay pure substring prefixes.

Regression test added: .claude/hooks/tests/test_require_skill_for_issue_create_gh_api.sh
covers GET on /contents, GET on /issues/<id>, POST on /pulls (not
/issues), POST on /issues via --method, POST on /issues via field
flag, and gh issue create. 6/6 PASS locally.

Closes #382

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #384

Commit: 584143789612292cc24f7b7648323870a1b76980

Summary

Surgical refinement of the gh api repos/ matcher in require-skill-for-issue-create.sh so it only blocks POSTs against /issues endpoints. Previously the pure-substring prefix caught read-only GETs (e.g. gh api repos/o/r/contents/README.md) as if they were ticket-create attempts. Closes #382.

Checklist Results

  • Architecture & Design: Pass
  • Code Quality: Pass
  • Testing: Pass
  • Security: Pass (the gate against actual /issues POSTs is preserved; the escape hatch and bootstrap exemption paths remain untouched)
  • Performance: Pass (one extra case statement on a code path that only runs after a match)
  • PR Description & Glossary: Pass (4 well-explained terms)
  • Summary Bullet Narrative: Pass (every bullet has what + why)
  • Technical Decisions (AgDR):N/A (surgical bug fix, not a new decision)
  • Adopter Handbooks: N/A (no handbooks loaded for shell-script hook fixes)

Diff correctness

  • Refinement block is placed exactly where it should be: AFTER MATCHED is determined (so we know we matched something) and BEFORE the bootstrap / active-issue-skill exemptions (so a non-/issues-write gh api call short-circuits cleanly and doesn't waste cycles on exemption resolution).
  • Case statement is properly scoped: "gh api"*) is the ONLY arm — all other matched patterns (gh issue create, linear issue create, jira issue create, asana task create) fall through unchanged. Surgical as advertised.
  • Write-signal enumeration is complete and matches the gh-cli reality: -X POST / -XPOST / --method POST / --method=POST covers explicit method flags (both equals and space forms); -f / -F / --field / --raw-field / --input covers field flags that implicitly switch gh from GET to POST.
  • NORM_CMD is whitespace-collapsed via tr -s '[:space:]' ' ' BEFORE the case match, so the pattern's required leading space (*" -f "* etc.) is reliable.
  • Block-list narrowing (is_issues_endpoint=1 AND is_write=1 both required) preserves the original protection against the /issues POST shape while no longer false-blocking innocent reads.

Test coverage

The new test_require_skill_for_issue_create_gh_api.sh covers the 6 cases enumerated in the issue body's "Test coverage worth carrying" section, mapped 1:1. Sandbox shape mirrors the existing test_require_skill_for_issue_create.sh test. The two existing tests (18/18 unchanged + 6/6 new) give full regression coverage.

Real-world bug confirmation

During this review I tried to read the post-fix hook source via gh api repos/.../contents/...sh to verify the file. The current (pre-merge) main-branch hook blocked the GET with BLOCKED: Raw ticket-create CLI detected (matched pattern: "gh api repos/") — exactly the bug this PR fixes. Live proof the issue is real and the fix is needed.

Issues Found

None.

Verdict

APPROVED


🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 584143789612292cc24f7b7648323870a1b76980

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #384

Commit: 584143789612292cc24f7b7648323870a1b76980

Summary

Surgical refinement of the gh api repos/ matcher in require-skill-for-issue-create.sh so it only blocks POSTs against /issues endpoints. Previously the pure-substring prefix caught read-only GETs (e.g. gh api repos/o/r/contents/README.md) as if they were ticket-create attempts. Closes #382.

Checklist Results

  • Architecture & Design: Pass
  • Code Quality: Pass
  • Testing: Pass
  • Security: Pass (the gate against actual /issues POSTs is preserved; the escape hatch and bootstrap exemption paths remain untouched)
  • Performance: Pass (one extra case statement on a code path that only runs after a match)
  • PR Description & Glossary: Pass (4 well-explained terms)
  • Summary Bullet Narrative: Pass (every bullet has what + why)
  • Technical Decisions (AgDR):N/A (surgical bug fix, not a new decision)
  • Adopter Handbooks: N/A (no handbooks loaded for shell-script hook fixes)

Diff correctness

  • Refinement block is placed exactly where it should be: AFTER MATCHED is determined (so we know we matched something) and BEFORE the bootstrap / active-issue-skill exemptions (so a non-/issues-write gh api call short-circuits cleanly and doesn't waste cycles on exemption resolution).
  • Case statement is properly scoped: "gh api"*) is the ONLY arm — all other matched patterns (gh issue create, linear issue create, jira issue create, asana task create) fall through unchanged. Surgical as advertised.
  • Write-signal enumeration is complete and matches the gh-cli reality: -X POST / -XPOST / --method POST / --method=POST covers explicit method flags (both equals and space forms); -f / -F / --field / --raw-field / --input covers field flags that implicitly switch gh from GET to POST.
  • NORM_CMD is whitespace-collapsed via tr -s '[:space:]' ' ' BEFORE the case match, so the pattern's required leading space (*" -f "* etc.) is reliable.
  • Block-list narrowing (is_issues_endpoint=1 AND is_write=1 both required) preserves the original protection against the /issues POST shape while no longer false-blocking innocent reads.

Test coverage

The new test_require_skill_for_issue_create_gh_api.sh covers the 6 cases enumerated in the issue body's "Test coverage worth carrying" section, mapped 1:1:

Issue body case Test case Expected rc
gh api repos/o/r/contents/... GET "GET on /contents is no-op (bug fix)" 0
gh api repos/o/r/issues/<id> GET "GET on /issues/ is no-op (no write signal)" 0
gh api repos/o/r/pulls -X POST -f ... "POST on /pulls is no-op (not an /issues endpoint)" 0
gh api repos/o/r/issues --method POST -f ... blocked "POST on /issues via --method is blocked" 2
gh api repos/o/r/issues -f title=x blocked "POST on /issues via -f field flag is blocked" 2
gh issue create unaffected "gh issue create still blocked (refinement only narrows gh api)" 2

Sandbox shape mirrors the existing test_require_skill_for_issue_create.sh test (per-case sandbox with onboarding.yaml + empty registry + hook + libs, synthetic PreToolUse Bash JSON via jq). The two existing tests (18/18 unchanged + 6/6 new) give full regression coverage.

Real-world bug confirmation

During this review I tried to read the post-fix hook source via gh api repos/.../contents/...sh to verify the file. The current (pre-merge) main-branch hook blocked the GET with BLOCKED: Raw ticket-create CLI detected (matched pattern: "gh api repos/") — exactly the bug this PR fixes. Live proof the issue is real and the fix is needed.

Issues Found

None.

Suggestions

None. The fix is minimal, well-commented (the inline rationale references the issue number directly), and tested.

Verdict

APPROVED (submitted as comment review because self-approve is blocked by GitHub)


🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 584143789612292cc24f7b7648323870a1b76980

@atlas-apex atlas-apex merged commit 9802dce into dev May 23, 2026
3 checks passed
@atlas-apex atlas-apex deleted the fix/GH-382-gh-api-matcher-refinement branch May 23, 2026 20:37
me2resh added a commit that referenced this pull request Jun 5, 2026
The pattern `gh api repos/` in ticket.create_command_patterns was
a pure substring prefix with no HTTP-method or endpoint refinement,
so any read-only `gh api repos/owner/repo/contents/...` GET was
blocked as if it were a ticket-create POST.

Add a case-match for the `gh api` family after $MATCHED is set: if
the command does not BOTH target /issues AND carry a write signal
(-X POST / --method POST / -f / -F / --field / --raw-field /
--input), downgrade the match to exit 0 (no-op).

Other tracker CLIs (gh issue create, linear issue create, jira
issue create, asana task create) are unaffected — their patterns
stay pure substring prefixes.

Regression test added: .claude/hooks/tests/test_require_skill_for_issue_create_gh_api.sh
covers GET on /contents, GET on /issues/<id>, POST on /pulls (not
/issues), POST on /issues via --method, POST on /issues via field
flag, and gh issue create. 6/6 PASS locally.

Closes #382

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants