Skip to content

claude-security-review: paths inputs document Actions paths: syntax but implement gitignore matching #289

Description

@kyle-sexton

Summary

claude-security-review.yml documents its paths / paths-file inputs as taking "GitHub Actions paths: filter syntax" (:155-156, :171-172 at c136b27), but :410-449 implements matching with git init + a root-anchored .gitignore + git check-ignore --stdin --no-index. Those are two different specifications.

Not a live behavior defect today — verified empirically below. Filing so the divergence is recorded before a consumer writes a pattern in the divergent set, because on claude-code-plugins this lane's check is REQUIRED, and a silently-non-matching pattern there means a security review that never fires.

Empirical result

I reproduced the workflow's pattern loop (:417-428) verbatim and ran git check-ignore against representative paths. The reproduction script and the anchoring logic are identical to the shipped implementation.

Anchoring works as intended for ordinary globs. Notably **/*.sh DOES match a root-level install.sh (gitignore treats a leading **/ as "any depth including root"), so the most obvious candidate defect does not exist:

FIRES     install.sh
FIRES     scripts/deploy.sh
FIRES     plugins/foo/hooks/x.js
FIRES     package.json
FIRES     sub/package.json
FIRES     .mcp.json
FIRES     REVIEW.md
FIRES     .github/workflows/ci.yml
NO-REVIEW docs/readme.md

Where the two specifications actually diverge

1. Negation (!) — the material one. Actions paths: supports negation with order-dependent semantics; the official docs state "A matching negative pattern (prefixed with !) after a positive match will exclude the path" and "The order that you define paths patterns matters." The implementation passes ! through to gitignore (:424), where order still matters but the containing-directory rule does not apply the same way. Measured:

patterns:  .github/**  then  !.github/docs/**
  FIRES  .github/docs/notes.md      <-- negation did NOT exclude it

A consumer writing an Actions-valid exclusion gets a pattern that silently does not exclude. On a repo where this check is required, that direction is at least fail-safe (more reviews, not fewer) — but it is not what the consumer wrote.

2. Character classes. [0-9] is undocumented for Actions paths: (the docs describe only * and **) but is fully supported by gitignore. Measured: src/file[0-9].ts matches src/file1.ts and not src/fileA.ts. A consumer could rely on behavior the documented spec does not promise.

3. Trailing-slash directory patterns. scripts/ is a gitignore directory-match idiom; Actions' docs do not address it. Measured as matching scripts/deploy.sh.

4. ? is listed for Actions branch/tag filters but omitted from the paths: description; gitignore supports it.

Live exposure: none today

melodic-software/claude-code-plugins ships the tuned paths file — 26 pattern entries (31 lines including 5 comment/blank). Checked every entry against all four divergent classes:

negation (!):        0
character class ([): 0
trailing-slash only: 0
question mark (?):   0

Every entry is an ordinary * / ** glob, where the two specifications agree. So this is a documentation defect, not a live behavior defect — but the gap is one ! line away from becoming one in the repo where the check is required.

Suggested fix

Correct the input descriptions to describe what is actually implemented. Options, in preference order:

  1. Document the real matcher — say the patterns are matched as root-anchored gitignore patterns, note that ! negation does not behave as Actions documents it, and drop the "Actions paths: syntax" claim. Cheapest and honest.
  2. Reject unsupported patterns loudly — fail the changes job on a ! line rather than silently mismatching. Fail-closed on a spec the implementation cannot honor.
  3. Implement true Actions paths: semantics. Most faithful, most work; probably not worth it given zero live usage of the divergent set.

Recommend (1) plus (2): document reality, and make the one materially divergent class loud instead of silent.

Related

No linked issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_013pLW2dybov9xvTFtx48Ueb

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: lowNice-to-have, cosmetic, or speculative; opportunistic.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions