Add CI symbol guard for critical hook files#105
Merged
virtualian merged 1 commit intomainfrom Apr 13, 2026
Merged
Conversation
Fail PRs that touch Releases/*/.claude/hooks/** and either fail to bun-build the critical hook files or drop one of the named symbols that a prior PR (#101 -> #104) silently deleted. Guarded symbols: - RatingCapture: CorrectionMode, BehavioralSignal, BEHAVIORAL_FEEDBACK_STATE - learning-readback: loadLatestSynthesis, loadBehavioralTrends, loadLearningDigest, loadWisdomFrames, loadFailurePatterns, loadSignalTrends - paths.ts: getConfigDir, getPaiDir, configPath, codePath The grep-based check is deliberately simple: if a PR legitimately needs to remove a guarded symbol, update this workflow in the same PR and justify the removal in the description. Build step additionally runs bun build --target=bun on each file to catch module-resolution regressions (missing imports, renamed exports) before they reach main. Path scoping: triggers only on PRs touching the release hooks tree or this workflow itself, so unrelated PRs don't pay the CI cost.
virtualian
added a commit
that referenced
this pull request
Apr 13, 2026
Supersedes PR #103. Incorporates the 4 correction commits from branch 100-claude-config-dir-pai-dir-separation (b3e4e49, 42f70b4, 39ea6b1, c7f304c) as a single focused refactor on top of the CLAUDE_CONFIG_DIR + PAI_DIR split that landed via #101 and the CorrectionMode + loadLatestSynthesis restore that landed via #104. Path separation fixes --------------------- - settings.json: revert 13 hook commands from CLAUDE_CONFIG_DIR back to PAI_DIR (the hook-root inversion originally introduced by ebc8130) - paths.ts: getHooksDir() returns codePath('hooks') not configPath('hooks'), docstring rewrites for the two-root contract - LoadContext.hook.ts: eliminate paiDir parameter chain across 5 helpers, route 5 raw join(paiDir, ...) calls through codePath(), rename misleading loadSettings param, extract sessionProgressPath - UpdateCounts.ts: countHooks and refreshUsageCache now read MEMORY from the PAI root instead of the CONFIG root - RelationshipMemory.hook.ts: ensureRelationshipDir fixed for the split - LastResponseCache.hook.ts: mkdirSync before writeFileSync (silent ENOENT on fresh install) - prd-utils.ts: correct replace() prefix from configPath to codePath - VoiceCompletion.hook.ts: replace hardcoded ~/.claude/settings.json with getSettingsPath() - VoiceNotification.ts, tab-setter.ts: drop dead configPath imports - DocCrossRefIntegrity.ts, change-detection.ts, identity.ts, notifications.ts, SessionCleanup.hook.ts, KittyEnvPersist.hook.ts, WorkCompletionLearning.hook.ts: small codePath / configPath cleanups - .pai-protected.json: correct stale comment Deprecated alias deletion ------------------------- - paths.ts: remove the paiPath() function entirely. The semantic flip in v4.0.3 (pre-split ~/.claude/x -> post-split ~/.pai/x) is exactly the class of change where a silent alias is a landmine, not a safety net. The compiler now catches any revenant caller. Zero code callers existed when this commit was written. - SYSTEM_USER_EXTENDABILITY.md: update 4 code examples from paiPath() to codePath() so the documentation matches the public API. MEMORY migration release note (manual upgrade path) --------------------------------------------------- - Releases/v4.0.3+/README.md: add a "Breaking Changes" subsection explaining the two-root split and add the required MEMORY migration step (`mv ~/.claude/MEMORY ~/.pai/MEMORY`) to the "Upgrading from v4.0.x" recipe. An automated migrator is a follow-up - see the tracking issue. Deferred (intentional) ---------------------- - statusLine.command still points at ${CLAUDE_CONFIG_DIR}/statusline-command.sh because the physical file currently lives at ~/.claude/statusline-command.sh. Relocating the script is a separate decision that does not belong in this refactor. Verification ------------ - bun build --target=bun clean on all 17 touched hook TypeScript files - settings.json parses as valid JSON - env-var resolution tested under CLAUDE_CONFIG_DIR=/tmp/ctest PAI_DIR=/tmp/ptest: 10/10 path primitives resolve to the expected roots - settings.json hook-command audit: 0 references to CLAUDE_CONFIG_DIR/hooks, 24 references to PAI_DIR/hooks - MEMORY routing audit: 0 configPath('MEMORY'), 45 codePath('MEMORY'), 0 raw ~/.claude/MEMORY references in hook sources - CI symbol guard (merged in #105) passes all 13 required-symbol checks against this commit
This was referenced Apr 13, 2026
virtualian
added a commit
that referenced
this pull request
Apr 13, 2026
) Supersedes PR #103. Incorporates the 4 correction commits from branch 100-claude-config-dir-pai-dir-separation (b3e4e49, 42f70b4, 39ea6b1, c7f304c) as a single focused refactor on top of the CLAUDE_CONFIG_DIR + PAI_DIR split that landed via #101 and the CorrectionMode + loadLatestSynthesis restore that landed via #104. Path separation fixes --------------------- - settings.json: revert 13 hook commands from CLAUDE_CONFIG_DIR back to PAI_DIR (the hook-root inversion originally introduced by ebc8130) - paths.ts: getHooksDir() returns codePath('hooks') not configPath('hooks'), docstring rewrites for the two-root contract - LoadContext.hook.ts: eliminate paiDir parameter chain across 5 helpers, route 5 raw join(paiDir, ...) calls through codePath(), rename misleading loadSettings param, extract sessionProgressPath - UpdateCounts.ts: countHooks and refreshUsageCache now read MEMORY from the PAI root instead of the CONFIG root - RelationshipMemory.hook.ts: ensureRelationshipDir fixed for the split - LastResponseCache.hook.ts: mkdirSync before writeFileSync (silent ENOENT on fresh install) - prd-utils.ts: correct replace() prefix from configPath to codePath - VoiceCompletion.hook.ts: replace hardcoded ~/.claude/settings.json with getSettingsPath() - VoiceNotification.ts, tab-setter.ts: drop dead configPath imports - DocCrossRefIntegrity.ts, change-detection.ts, identity.ts, notifications.ts, SessionCleanup.hook.ts, KittyEnvPersist.hook.ts, WorkCompletionLearning.hook.ts: small codePath / configPath cleanups - .pai-protected.json: correct stale comment Deprecated alias deletion ------------------------- - paths.ts: remove the paiPath() function entirely. The semantic flip in v4.0.3 (pre-split ~/.claude/x -> post-split ~/.pai/x) is exactly the class of change where a silent alias is a landmine, not a safety net. The compiler now catches any revenant caller. Zero code callers existed when this commit was written. - SYSTEM_USER_EXTENDABILITY.md: update 4 code examples from paiPath() to codePath() so the documentation matches the public API. MEMORY migration release note (manual upgrade path) --------------------------------------------------- - Releases/v4.0.3+/README.md: add a "Breaking Changes" subsection explaining the two-root split and add the required MEMORY migration step (`mv ~/.claude/MEMORY ~/.pai/MEMORY`) to the "Upgrading from v4.0.x" recipe. An automated migrator is a follow-up - see the tracking issue. Deferred (intentional) ---------------------- - statusLine.command still points at ${CLAUDE_CONFIG_DIR}/statusline-command.sh because the physical file currently lives at ~/.claude/statusline-command.sh. Relocating the script is a separate decision that does not belong in this refactor. Verification ------------ - bun build --target=bun clean on all 17 touched hook TypeScript files - settings.json parses as valid JSON - env-var resolution tested under CLAUDE_CONFIG_DIR=/tmp/ctest PAI_DIR=/tmp/ptest: 10/10 path primitives resolve to the expected roots - settings.json hook-command audit: 0 references to CLAUDE_CONFIG_DIR/hooks, 24 references to PAI_DIR/hooks - MEMORY routing audit: 0 configPath('MEMORY'), 45 codePath('MEMORY'), 0 raw ~/.claude/MEMORY references in hook sources - CI symbol guard (merged in #105) passes all 13 required-symbol checks against this commit
This was referenced Apr 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
.github/workflows/hook-symbol-guard.yml— a minimal CI job that fails PRs touchingReleases/*/.claude/hooks/**if either of the following is true:bun build --target=bunfails on any of the critical hook files (RatingCapture.hook.ts,learning-readback.ts,paths.ts,LoadContext.hook.ts).Motivation
PR #101 squash-merged a four-commit change into
mainwhose bundled commitebc8130silently deleted ~606 lines of unrelated functionality (CorrectionModefast-path,BehavioralSignalcapture,loadLatestSynthesis). PR #104 restored those files, but nothing structural prevents the same class of failure from happening again.This workflow is the structural fix: a deletion of any guarded symbol — whether by a human, an agent, or an IDE refactor — fails the PR with a clear message before review.
Guarded symbols
RatingCapture.hook.tsCorrectionMode,BehavioralSignal,BEHAVIORAL_FEEDBACK_STATElib/learning-readback.tsloadLatestSynthesis,loadBehavioralTrends,loadLearningDigest,loadWisdomFrames,loadFailurePatterns,loadSignalTrendslib/paths.tsgetConfigDir,getPaiDir,configPath,codePath13 symbol checks total.
How to remove a symbol legitimately
If a future PR needs to remove a guarded symbol on purpose, the contract is: update this workflow in the same PR to remove the symbol from the check list, and justify the removal in the PR description. The grep check is deliberately simple so this escape hatch is obvious.
Scope and cost
actions/checkout@v4andoven-sh/setup-bun@v2.Self-test evidence (from local working tree on fresh
origin/main)bun yaml parseon the workflow file: valid, 4 steps detected.origin/main.bun build --target=bunclean on all 4 critical files.Known limitations
Releases/v4.0.3+/is hard-coded. When a futureReleases/v4.0.4+/tree appears, this workflow will need updating. That's an intentional simplification — much better to maintain a short explicit list than to ship a clever-but-brittle glob.bun buildstep when its call sites break.