fix(work-items): resolve consumer-local gh-bot.sh wrapper independent of adapter location - #826
Conversation
… of adapter location
The GitHub adapter's WIT_GH_BOT resolved the bot wrapper relative to the
adapter's own directory, so a consuming repo's wrapper at
${CLAUDE_PROJECT_DIR}/tools/github-auth/gh-bot.sh — the override path
CONTRACT.md already documented — was never found, and tracker writes silently
fell back to the ambient gh (session-user) identity.
wit_gh_resolve_bot_wrapper now checks the consumer-local path first, falling
back to the plugin-bundled path, mirroring the adapter's own
consumer-local-first/plugin-bundled-fallback resolution.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
|
Claude finished @kyle-sexton's task in 1m 53s —— View job PR Review — fix(work-items): consumer-local gh-bot.sh wrapper resolution
Assessment: ApproveThe fix is correct, minimal, and architecturally consistent. The bug (adapater-relative-only resolution of common.sh —
|
|
Claude finished @kyle-sexton's task in 2m 20s —— View job Security Review
|
|
Post-green review classification (babysit lane): Both automated PR reviews returned Approve / Good to merge with no blocking findings. Both automated security-review runs raised one identical Low-confidence defense-in-depth item, classified below.
No blocking or defer-forbidden item applies. No inline review threads exist on this PR (GraphQL query returned empty) — nothing to resolve. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Claude finished @kyle-sexton's task in 1m 55s —— View job Security Review
FindingsSUGGESTION —
|
|
Claude finished @kyle-sexton's task in 1m 33s —— View job PR Review — fix(work-items): consumer-local gh-bot.sh wrapper resolution
Assessment: ApproveThis is a clean, targeted bug fix. The root cause (adapter-relative-only
|
…de-ops, work-items Re-checked open PRs touching these plugins' plugin.json right before the version-bump collision protocol requires it: source-control carries #839 (0.15.8) and #840 (0.15.9), claude-ops carries #844 (0.17.2), work-items carries #826 (0.18.2) — all still open. Bumps this PR's claims one past each plugin's current highest open-PR claim (source-control 0.15.10, claude-ops 0.17.3, work-items 0.18.3) so this PR does not collide at merge time regardless of which sibling lands first. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
|
Tower merge-sit: merge-drive lane dark ~5.5h (rate-limit hold since ~10:36Z); tower sitting merges per stall protocol. Gate-verified live: CLEAN, 0 unresolved threads. This was generated by AI (control tower, session 6). |
Resolves conflicts on source-control and work-items plugin.json/CHANGELOG.md: main advanced source-control to 0.15.8 (#839, merged) and work-items to 0.18.2 (#826, merged) since this branch was last rebased. Also re-checked live open PRs at merge time and found work-items now carries a new open claim, #857 at 0.19.0 (Jira adapter) — re-bumped this branch's work-items claim from 0.18.3 to 0.19.1 to stay ahead of it. source-control's 0.15.10 and claude-ops's 0.17.3 remain valid (still one past #840/#860's 0.15.9 and #844/#860's 0.17.2 open claims, respectively). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
… as deliberate (#853) ## Summary Closes the design fork raised in #820: whether the shell assert-helper duplicated across 5 plugins (and the divergent per-script exit-code taxonomies alongside it) should be consolidated into a shared mechanism, or documented as deliberate. This PR documents. ## Fix - Adds `docs/conventions/shell-test-helpers/README.md` as the owner doc explaining why the duplication and divergence stay as-is, and registers it in `docs/PLUGIN-PHILOSOPHY.md`'s convention registry table. - Adds a one-line pointer comment at each copy site back to the owner doc: `guardrails/hooks/guardrails-test-helpers.sh`, `claude-ops/hooks/claude-ops-test-helpers.sh`, `source-control/scripts/test-helpers.sh`, `repo-hygiene/skills/clean/scripts/lib/test-helpers.sh`, `work-items/tools/work-item-tracker/tests/lib.sh`. - Adds the same kind of pointer to `scripts/check-skill-portability.test.sh`, which opted out for an unrelated reason (it's repo tooling, not a plugin, so no plugin assertion library applies) — noted so the fork's second observation isn't left unexplained. - Bumps `plugin.json` + adds a `CHANGELOG.md` entry for every plugin whose helper file gained the pointer comment. Each has been re-derived from current `main` many times as sibling PRs merged or rebased mid-flight (see Related for the current picture): - `repo-hygiene`: 0.4.4→0.4.6 - `source-control`: 0.15.7→0.16.1 - `claude-ops`: 0.17.1→0.17.5 - `work-items`: 0.18.1→0.20.1 - `guardrails`: 0.9.5→0.9.6 (no open-PR collision at any point so far) No behavior change anywhere — comments and docs only. ## Decision **Chose Option B: document the per-plugin duplication and exit-code divergence as deliberate. No shared helper introduced.** Investigation before deciding: - This repo already has one sanctioned cross-plugin shared-source mechanism: a canonical file under `lib/` (e.g. `lib/hook-utils.sh`), copied — not imported — into each carrying plugin by a dedicated `scripts/sync-*.sh`, tracked in `scripts/cross-plugin-source-registry.txt`, and drift-checked by `scripts/check-cross-plugin-source-drift.sh --check`. - That mechanism is scoped to clusters meant to stay **byte-identical**. Running `check-cross-plugin-source-drift.sh discover` confirms it never even flags the five assert-helper files as a cluster candidate — they live at different paths per plugin and are not byte-identical, so they fall outside that mechanism's scope entirely. - Reading all five files: they are already three genuinely different shapes, not one library that drifted — a hook-contract shape (`guardrails`/`claude-ops`: `ok`/`bad`, `PASS`/`FAIL`, `make_sink`/`wait_for_sink`), a skill-script shape (`source-control`/`repo-hygiene`: `pass`/`fail`, `FAILED`/`CASE_NUM`, file-existence assertions), and a vendored-seam shape (`work-items`: same primitives, but owned by the seam itself so it stays correct wherever the seam is resolved from, independent of this repo's tooling). - Consolidating would mean designing a fourth, unified assertion API and rewriting every existing `*.test.sh` onto it — a bigger, riskier change than the coupling it would remove, and it would cross the plugin-independence boundary `docs/PLUGIN-PHILOSOPHY.md`'s design boundary section already draws (no plugin imports files from a sibling plugin). - Exit-code taxonomies (`remove-path.sh` 0/1/2/3/4, `git-tree-reset-batch.sh` 0/1/2 forwarding a child's 5/7, `check-skill-portability.sh` 0/1/2) encode genuinely different per-script contracts, not arbitrary numbering — each script already documents its own `Exit:` line, and a shared usage/exit helper would either flatten those contracts or grow per-caller branching. - Deferred, not rejected: `guardrails-test-helpers.sh` and `claude-ops-test-helpers.sh` are the one pair that already share a shape closely. If they converge to byte-identical, vendoring just that pair through the existing `lib/` + `sync-*.sh` + registry mechanism is the smaller, precedented move — recorded as the trigger in the owner doc rather than acted on now. ## Verification - `shellcheck` clean on all 6 edited shell files. - Full `check-skill-portability.test.sh` suite: 16/16 pass. - `check-cross-plugin-source-drift.sh --check`: no unregistered or drifted clusters. - `check-changelog-parity.sh --check`: passes with every version bump. - Ran every `*.test.sh` that sources an edited helper (repo-hygiene/clean, guardrails hooks, claude-ops hooks, work-items adapters/lib) — all green, confirming the comment-only edits changed no behavior. - `markdownlint-cli2` and `lychee` clean on the new and modified docs. ## Related - Scope note: issue #820's title says "disk-hygiene/clean", but `disk-hygiene` is Python-only (`hygiene.py`) with no shell assert-helper — the actual duplication lives in the 5 plugins the issue body names (repo-hygiene, source-control, guardrails, claude-ops, work-items) plus root `scripts/`. Treating the title as a triage typo (disk-hygiene vs. repo-hygiene, both "-hygiene" plugins with a `clean` skill) rather than touching disk-hygiene. - **`do-not-merge` held.** This session has had exceptionally heavy concurrent-lane traffic against these same 5 plugins — this PR has been rebased/re-derived nine times as siblings merged (#839, #826, #857, #877, #870, #844) or rebased in place (#840, #882, #861, each more than once). Current picture, last verified fresh at commit `e75e45ed` (`mergeable: MERGEABLE`; all 5 plugins re-checked against current `main` AND every live open PR): - `repo-hygiene` (claims 0.4.6): no open-PR collision. Main is at 0.4.5. - `source-control` (claims 0.16.1): held behind **#882** (`fix/511-babysit-self-identity-decouple`, claims 0.16.0, open) and **#840** (claims 0.15.10, open). Main is at 0.15.9. - `claude-ops` (claims 0.17.5): **no open-PR collision anymore** — #844 (the PR this leg was previously held behind) has merged, landing at exactly 0.17.4; this claim stays one past it. Main is at 0.17.4. - `work-items` (claims 0.20.1): held behind **#861** (`feat/613-mini-sdlc-pipeline-ssot`). #861 itself has re-derived its claim twice as `main` moved — from 0.19.0 up to 0.20.0 (following #857's Jira-adapter minor bump into main) — so this PR's claim moved from 0.19.1 to 0.20.1 to stay ahead. Main is at 0.19.0. - `guardrails` (claims 0.9.6): no open-PR collision. Main is at 0.9.5. - This plugin set has produced a new collision within minutes of nearly every prior check — including siblings re-deriving their own claims upward more than once, an unrelated PR landing at the exact same version by coincidence, and legs clearing and new ones opening. Re-run the full collision protocol (`gh pr list --repo melodic-software/claude-code-plugins --state open --json number,headRefName,files` filtered per plugin, AND a fresh diff of each plugin's version on `main` since this PR's last rebase) immediately before removing `do-not-merge` — do not trust this snapshot. Closes #820 Work-class: C2 (mechanical) — attended triage 2026-07-23, operator-ratified. 🤖 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Summary
WIT_GH_BOTresolved the bot wrapper relative to the adapter's own directory (${CLAUDE_PLUGIN_ROOT}/tools/github-auth/gh-bot.shin the normal bundled path), never checking the consumer-local override path CONTRACT.md already documented (${CLAUDE_PROJECT_DIR}/tools/github-auth/gh-bot.sh).gh(session-user) identity instead of bot attribution.Fix
common.sh: extractedwit_gh_resolve_bot_wrapper, which now resolves the bot wrapper consumer-local-first, plugin-bundled fallback — checking${CLAUDE_PROJECT_DIR}/tools/github-auth/gh-bot.shfirst (independent of where the adapter itself resolved from, so a shadowed consumer-local adapter still finds the consumer's wrapper), then falling back to the bundled path beside the seam tree. This mirrors the existing two-rule adapter resolution documented in CONTRACT.md's "Adapter resolution" section.CONTRACT.md: updated the "Identity routing (GitHub adapter)" section to document the corrected consumer-local-first/plugin-bundled-fallback resolution order (previously it documented the buggy adapter-relative-only behavior).common.test.sh: added regression coverage forwit_gh_resolve_bot_wrapper— resolves the consumer-local wrapper when present, falls back to the bundled path when the consumer has none, and falls back whenCLAUDE_PROJECT_DIRis unset.Verification
Full adapter + dispatcher test suite run, all green:
All other
adapters/github/*.test.shfiles andwork-item-tracker.test.sh(including its own consumer-local/adapter-resolution cases) also pass unchanged.shellcheckon both modified shell files: no findings.Closes #365
Related