post-push hook never ran: git has no such hook, so no plugin cache was refreshed #78

Closed
opened 2026-07-05 19:59:40 +00:00 by Claude · 1 comment
Collaborator

Context

scripts/git-hooks/post-push only runs claude plugin update kyberforge after a push. It doesn't update the gitea plugin.

Found while closing out #67 (the gitea deep-modules refactor): after merging that PR, the installed cache at ~/.claude/plugins/cache/holocron/gitea/ was still pinned at 1.0.0 — the pre-refactor version with no skills/ directory at all. None of the new skills (gitea-issues, gitea-prs, gitea-workflow, etc.) were available in the session until manually refreshed, because the hook that's supposed to keep plugin caches current silently skips this plugin.

Impact

Any session (this machine or another) that pushes to main gets its kyberforge cache refreshed automatically but not gitea — changes to gitea skills/agents merged to main don't take effect until someone notices and runs claude plugin update gitea by hand. This already caused a stale-skill situation immediately after #67 merged.

Fix

Update scripts/git-hooks/post-push to update gitea alongside (or instead of hardcoding kyberforge, e.g. iterate all installed plugins tracked by the holocron marketplace, or accept a list). Should probably also cover bin, core, git for the same reason unless there's a reason kyberforge was special-cased.

## Context `scripts/git-hooks/post-push` only runs `claude plugin update kyberforge` after a push. It doesn't update the `gitea` plugin. Found while closing out #67 (the gitea deep-modules refactor): after merging that PR, the installed cache at `~/.claude/plugins/cache/holocron/gitea/` was still pinned at `1.0.0` — the pre-refactor version with no `skills/` directory at all. None of the new skills (`gitea-issues`, `gitea-prs`, `gitea-workflow`, etc.) were available in the session until manually refreshed, because the hook that's supposed to keep plugin caches current silently skips this plugin. ## Impact Any session (this machine or another) that pushes to `main` gets its `kyberforge` cache refreshed automatically but not `gitea` — changes to gitea skills/agents merged to main don't take effect until someone notices and runs `claude plugin update gitea` by hand. This already caused a stale-skill situation immediately after #67 merged. ## Fix Update `scripts/git-hooks/post-push` to update `gitea` alongside (or instead of hardcoding `kyberforge`, e.g. iterate all installed plugins tracked by the holocron marketplace, or accept a list). Should probably also cover `bin`, `core`, `git` for the same reason unless there's a reason `kyberforge` was special-cased.
Claude added the Kind/Bug
Priority
Medium
3
labels 2026-07-05 19:59:40 +00:00
Defame1297 added this to the Tooling milestone 2026-08-10 19:11:12 +00:00
Author
Collaborator

The premise here is wrong, and in a more interesting way than "outdated"

This issue says the hook refreshes kyberforge automatically but skips gitea. It skips everything: git has no client-side post-push hook. It is not in githooks(5) and git 2.39.5 never invokes one.

scripts/install.sh copies every file in scripts/git-hooks/ into .git/hooks/, so .git/hooks/post-push existed on disk and looked installed. It had never fired once, from the day it was added. The stale gitea cache after #67 was not a special case — it was the only observable symptom of a mechanism that had never worked at all.

Two tests appeared to cover it and did not:

  • tests/test-post-push.sh invoked the script directly with mocked git/claude and asserted its behaviour.
  • tests/test-git-hooks-install.sh asserted only that install.sh copied the file into .git/hooks/.

Neither asserted that git ever runs it. That gap is why this sat undetected for six weeks.

The fix is not the one proposed here

This issue proposes iterating all installed plugins instead of hardcoding kyberforge. That would have produced a correct list inside a script that still never executes.

Refreshing on push is also the wrong trigger regardless of hook mechanics: your install goes stale when someone else merges, so a push of your own is neither necessary nor sufficient for staleness to have occurred.

claude plugin update is moot besides — ADR-0018 moved this repo to consuming its own packages through apm, so there is no plugin cache to refresh.

What replaces it

ADR-0019. kyberforge ships a SessionStart hook that runs apm outdated and, when anything is behind, runs apm update --yes and returns reloadSkills: true so the running session picks up the redeployed content. SessionStart is the right trigger because the thing that goes stale is the skill content a session loads, and it can do two things a git hook structurally cannot: put the notice into the agent's context, and reload skills without a restart.

Measured: ~0.7 s when current, ~10.4 s when six packages are behind and the refresh runs.

scripts/git-hooks/post-push and tests/test-post-push.sh are deleted. install.sh's copy block is generic and kept, with test-git-hooks-install.sh now synthesizing its own fixture hook so the mechanism stays tested without a dead hook to prop it up.

Two related findings from the same work, both verified rather than assumed:

  • apm resolves ${CLAUDE_PLUGIN_ROOT} against the installed package root, and apm pack keeps only *.json out of .apm/hooks/ — so a .../hooks/<script> reference points into the generated mirror where the script does not exist. apm prints Hook script not found and deploys a hook aimed at nothing. A test now pins the .apm/-relative form.
  • apm's executable-trust gate is off by default. apm approve --list reports Executable-trust gate disabled -- all executables deploy until an executables: block exists in apm.yml. Every hook, bin, and MCP primitive a dependency shipped would have deployed unprompted. Now enabled for this repo.

Closed by PR #98.

## The premise here is wrong, and in a more interesting way than "outdated" This issue says the hook refreshes `kyberforge` automatically but skips `gitea`. It skips everything: **git has no client-side `post-push` hook.** It is not in `githooks(5)` and git 2.39.5 never invokes one. `scripts/install.sh` copies every file in `scripts/git-hooks/` into `.git/hooks/`, so `.git/hooks/post-push` existed on disk and looked installed. It had never fired once, from the day it was added. The stale `gitea` cache after #67 was not a special case — it was the only observable symptom of a mechanism that had never worked at all. Two tests appeared to cover it and did not: - `tests/test-post-push.sh` invoked the script directly with mocked `git`/`claude` and asserted its behaviour. - `tests/test-git-hooks-install.sh` asserted only that install.sh copied the file into `.git/hooks/`. Neither asserted that git ever runs it. That gap is why this sat undetected for six weeks. ## The fix is not the one proposed here This issue proposes iterating all installed plugins instead of hardcoding `kyberforge`. That would have produced a correct list inside a script that still never executes. Refreshing on push is also the wrong trigger regardless of hook mechanics: your install goes stale when **someone else** merges, so a push of your own is neither necessary nor sufficient for staleness to have occurred. `claude plugin update` is moot besides — ADR-0018 moved this repo to consuming its own packages through apm, so there is no plugin cache to refresh. ## What replaces it ADR-0019. kyberforge ships a `SessionStart` hook that runs `apm outdated` and, when anything is behind, runs `apm update --yes` and returns `reloadSkills: true` so the running session picks up the redeployed content. `SessionStart` is the right trigger because the thing that goes stale is the skill content a *session* loads, and it can do two things a git hook structurally cannot: put the notice into the agent's context, and reload skills without a restart. Measured: ~0.7 s when current, ~10.4 s when six packages are behind and the refresh runs. `scripts/git-hooks/post-push` and `tests/test-post-push.sh` are deleted. `install.sh`'s copy block is generic and kept, with `test-git-hooks-install.sh` now synthesizing its own fixture hook so the mechanism stays tested without a dead hook to prop it up. Two related findings from the same work, both verified rather than assumed: - apm resolves `${CLAUDE_PLUGIN_ROOT}` against the installed package root, and `apm pack` keeps only `*.json` out of `.apm/hooks/` — so a `.../hooks/<script>` reference points into the generated mirror where the script does not exist. apm prints `Hook script not found` and deploys a hook aimed at nothing. A test now pins the `.apm/`-relative form. - **apm's executable-trust gate is off by default.** `apm approve --list` reports `Executable-trust gate disabled -- all executables deploy` until an `executables:` block exists in `apm.yml`. Every hook, bin, and MCP primitive a dependency shipped would have deployed unprompted. Now enabled for this repo. Closed by PR #98.
Claude changed title from post-push hook doesn't refresh the gitea plugin cache to post-push hook never ran: git has no such hook, so no plugin cache was refreshed 2026-08-14 17:59:25 +00:00
Sign in to join this conversation.