setup.sh: merge user hooks instead of overwriting (DPLAN-0234 Strand C fix)

- AIPass bridge entries (bridges/claude.py marker) refreshed on every install; user-wired hooks and custom events preserved
- Fixture-verified: customs survive, stale bridge entries replaced no-dupe, fresh-install output shape-identical
- Context: full 17/17 hook fire-test passed on fresh Linux Docker install
This commit is contained in:
AIOSAI
2026-07-05 17:15:38 -07:00
parent 2ac499d9a3
commit 18dea21726
2 changed files with 28 additions and 1 deletions
+14
View File
@@ -11,6 +11,20 @@ PyPI version — not the changelog header.
## [2026-07-05]
### Fixed
- **Installer no longer destroys a user's custom Claude Code hooks (DPLAN-0234
Strand C).** setup.sh used to write `settings["hooks"]` wholesale — anyone
with their own hooks in `~/.claude/settings.json` lost them on install or
re-run. Now it merges: every AIPass bridge entry (identified by the
`bridges/claude.py` marker) is refreshed, while user-wired hooks and custom
events are preserved. Verified against fixtures: custom hooks survive, stale
AIPass entries are replaced without duplicates, and the fresh-install output
is shape-identical to before (7 events, 6 UserPromptSubmit + 6 PreCompact
entries). Found while fire-testing a fresh install's hooks in Docker — all
17 wired hook entries pass on a cold Linux clone (real kernel/navmap/branch
prompt bytes, git gate blocks, clean no-ops on empty state).
### Added
- **`./aipass` — repo-root cold-clone launcher (DPLAN-0234 Strand B).** The
+14 -1
View File
@@ -657,7 +657,7 @@ else:
# UserPromptSubmit: 5 separate entries (EventType:hook_name) to avoid output merging
# PreToolUse, PostToolUse, SubagentStop, Stop, Notification: single aggregate entries
# PreCompact: 3 hooks x 2 matchers (manual + auto) = 6 entries
settings["hooks"] = {
aipass_hooks = {
"UserPromptSubmit": [
{"hooks": [{"type": "command", "command": f"{bridge} UserPromptSubmit:tier0_kernel"}]},
{"hooks": [{"type": "command", "command": f"{bridge} UserPromptSubmit:navmap"}]},
@@ -693,6 +693,19 @@ settings["hooks"] = {
],
}
# Merge, don't replace (DPLAN-0234 Strand C): refresh every AIPass bridge entry
# (identified by the bridges/claude.py marker) but preserve any hooks the user
# wired themselves. Re-runs stay idempotent; custom hooks survive reinstall.
existing_hooks = settings.get("hooks", {})
merged_hooks = {}
for event in set(existing_hooks) | set(aipass_hooks):
user_entries = [
entry for entry in existing_hooks.get(event, [])
if "bridges/claude.py" not in json.dumps(entry)
]
merged_hooks[event] = aipass_hooks.get(event, []) + user_entries
settings["hooks"] = merged_hooks
# Inject AIPASS_HOME into env block so dispatched agents find AIPass
env_block = settings.get("env", {})
env_block["AIPASS_HOME"] = repo_root