Skip to content

Fix command reference paths so they resolve outside a plugin install - #9

Merged
MRagabMob merged 2 commits into
mainfrom
fix-command-reference-paths
Aug 13, 2026
Merged

MRagabMob merged 2 commits into
mainfrom
fix-command-reference-paths

Conversation

@MRagabMob

Copy link
Copy Markdown
Collaborator

Bug fix for the commands added in #7. Found by installing the skill the way a user actually would.

The bug

${CLAUDE_PLUGIN_ROOT} is substituted only in plugin skills. All three commands used it as their sole path to the reference files:

Read `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/references/test-credentials.md` in full

Ask an agent to install this skill from GitHub and it does not perform a marketplace plugin install. Verified on Claude Code: it copied the files as a personal skill to ~/.claude/skills/paymob-integration/ and the three command files into ~/.claude/commands/ (creating that directory). In that shape the variable is never expanded — it reaches the model as literal text, and every reference-file read fails.

Confirmed on disk after a real install: all three installed command files still contain the raw ${CLAUDE_PLUGIN_ROOT} string, while the reference files sit at ~/.claude/skills/paymob-integration/references/.

Why this is not cosmetic

For /paymob-test-cards and /paymob-explain-error the agent probably recovers by searching, and test-cards already forbids reciting numbers from memory, so its worst case is a refusal.

/paymob-check-hmac is the problem. It exists to verify a field order it deliberately does not carry, and instructs the agent to read hmac-verification.md rather than trust recall. If that read fails and the agent proceeds anyway, it audits the field order from pretraining — and can pass a wrong order as correct. That is HMAC verification that looks verified and silently is not, which is the single worst outcome in this domain, reached through nothing more than a broken file path.

Fix

Each command now resolves its root from the first location that exists:

Install shape Location
Plugin (marketplace / --plugin-dir) ${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/
Personal skill (the ask-an-agent path) ~/.claude/skills/paymob-integration/
Repository checkout skills/paymob-integration/

with a filename search as a last resort, an explicit note that an unexpanded ${CLAUDE_PLUGIN_ROOT} means "not a plugin install, use another path", and — most importantly — fail closed: if the file cannot be read, say so and stop rather than answer from memory. /paymob-check-hmac states this in its non-negotiable ground rules, not just in a step.

allowed-tools gains Glob (and Grep for the audit), because Read alone would have blocked the very fallback search the fix depends on.

The one-source-of-truth rule is untouched: no command carries a copy of the field order, endpoints, or test cards. Only the locating instruction changed.

Validator coverage

Two new per-command assertions in validate_commands, both negative-tested — I broke each rule and confirmed CI would catch it:

  • Naming ${CLAUDE_PLUGIN_ROOT} without a non-plugin fallback path fails: names ${CLAUDE_PLUGIN_ROOT} without a non-plugin fallback path; the reference files are unreachable in a personal-skill install
  • Dropping the fail-closed clause fails: must tell the agent to stop rather than answer from memory when it cannot read its reference file

Notes

  • disable-model-invocation: true stays on paymob-check-hmac. Its absence from the model's skill listing is correct by design — still user-invocable, just not offered for automatic invocation. Please don't "fix" that.
  • Does not touch skills/paymob-integration/**, the ?partner=claude parameter, or any manifest. No version bump: 3.3.0 is on main unreleased.
  • Touches commands/paymob-test-cards.md, which Document that kiosk and BNPL have no sandbox test path; fix /paymob-test-cards overpromising #8 also edits on different lines. Whichever merges second may need a trivial rebase; happy to do it.

Test plan

  • python scripts/validate.py passes
  • python scripts/package_skill.py builds
  • Both new checks negative-tested, then working tree confirmed clean
  • End-to-end, recommended before merge: remove ~/.claude/skills/paymob-integration and ~/.claude/commands/paymob-*.md, reinstall from this branch, start a fresh session, and invoke all three commands to confirm each reads its reference file. /paymob-check-hmac is the one to watch.

@MRagabMob

Copy link
Copy Markdown
Collaborator Author

End-to-end verification done — both cases pass

Ran two fresh agents with no prior context, each fed a command body exactly as a slash-command invocation injects it, in the personal-install shape (${CLAUDE_PLUGIN_ROOT} left unexpanded). Both were told their working directory was an unrelated project with no paymob checkout, so the repo-relative candidate could not rescue them — isolating the ~/.claude/skills/ path this PR adds.

Test 1 — does resolution work? Pass.

  • Candidate 1 correctly skipped, not guessed at: "This is a personal command, so the variable is unexpanded literal text and is not a resolvable path."
  • Candidate 2 resolved: C:\Users\DELL\.claude\skills\paymob-integration\references\hmac-verification.md, read, 73 lines.
  • Reported the concatenation order as 20 fields starting amount_cents, created_at, currency… — matching the spec exactly, which proves it read the file rather than recalling it.

Before this PR that read fails outright.

Test 2 — does it fail closed? Pass, and more robustly than I expected.

Given a command whose reference filename doesn't exist anywhere, the agent exhausted seven strategies — all three candidates, a scoped glob, a whole-home glob, find across the user profile, then enumerating all 12 paymob-integration directories on the machine and grepping each references/. It found two similarly named files nearby (hmac-verification.md and a hmac-validation.md in another checkout) and explicitly refused to substitute either:

"I did not open hmac-verification.md or hmac-validation.md. They are different filenames, and substituting a similar-looking spec is exactly what would defeat the command's purpose."

Its stated next action: stop, do not audit, report that the spec could not be read. Asked directly whether the instructions permitted proceeding from its own knowledge of the field order, it answered no and quoted the ground rule. That is the behavior this PR exists to guarantee.

One fix that came out of testing

Test 1 resolved correctly but had to expand ~ itself, and flagged that a strict reading of the literal ~/.claude/… string would fail on Windows. All three commands now name ~ as the home directory, give the %USERPROFILE% form, and mark candidate 3 as only resolving inside the skill repo — so candidate 2 is understood as the reliable one for a personal install. Pushed as a follow-up commit.

Rebased onto main after #8 merged

#8 and this PR both touched commands/paymob-test-cards.md frontmatter, exactly the collision I flagged. Resolved as the union of both: #8's corrected description and argument-hint ([card|wallet|kiosk|bnpl]) plus this PR's allowed-tools: Read Glob. Neither side's improvement was lost; validate.py passes after the rebase.

${CLAUDE_PLUGIN_ROOT} is substituted only for plugin skills. When an agent
is asked to install this skill from GitHub it copies the files as a
personal skill under ~/.claude/skills/ and the commands into
~/.claude/commands/, where the variable is never expanded and every
reference-file read fails.

For /paymob-check-hmac that failure is dangerous rather than cosmetic: the
command exists to check a field order it deliberately does not carry, so a
failed read invites an audit from recall that can pass a wrong order as
correct — HMAC verification that looks verified and is not.

Each command now resolves its root from the plugin root, the personal
skills directory, or a repo checkout, searches as a last resort, and stops
rather than answering from memory. allowed-tools gains Glob so the search
is actually permitted. validate.py enforces both properties.
A fresh agent run through the personal-install path resolved the file
correctly but had to expand "~" itself, and noted that a strict reading
of the literal "~/.claude/..." string would fail on Windows. Name "~" as
the home directory, give the %USERPROFILE% form, and mark path 3 as
working only inside the skill repo so path 2 is understood as the
reliable one for a personal install.
@MRagabMob
MRagabMob force-pushed the fix-command-reference-paths branch from e25ffde to 60cbd8b Compare August 13, 2026 16:26
@MRagabMob
MRagabMob merged commit 992b733 into main Aug 13, 2026
1 check passed
@MRagabMob
MRagabMob deleted the fix-command-reference-paths branch August 13, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant