Development record
hook salvage entries invoke bootstrap.sh with no timeout budget below its 180s curl worst case so a slow-link provision is killed and suppressed for ten minutes
hook salvage entries invoke bootstrap.sh with no timeout budget below its 180s curl worst case so a slow-link provision is killed and suppressed for ten minutes
Evidence
hooks/hooks.json — SessionStart (:18) declares "timeout":240; the four salvage entries (UserPromptSubmit :3, PreToolUse :24, PreCompact :35, SessionEnd :45) invoke the same bootstrap.sh with NO timeout, inheriting the harness 60s default (dcn-2608010000000007 records the 60s default and the 180s worst case as the reason SessionStart got 240). On a slow link the salvage hook is killed at 60s after .bootstrap.attempt is stamped, suppressing retry for 10 min, output to /dev/null.
Adversarial verdict: CONFIRMED (minor) — RECORD-ONLY
Default-timeout premise verified (60s, dcn-2608010000000007 + the hooks reference). State is kill-safe (trap cleanup; verified-then-mv install; no corrupt binary). Not fixed this round because the correct value is a design judgment: setting 240 on UserPromptSubmit would quadruple the user's synchronous prompt stall to 4 minutes. Recommended direction (for a human): a modest explicit timeout (90-120s) on the salvage entries, and — separately valuable — a test asserting the per-event timeout values (none exists today; even SessionStart's 240 is unpinned). No test currently pins any timeout field.
Grounds
- pursued: a slow-link salvage gets 120s with a visible spinner message instead of the host default and a silent stall; hooks_timeout_test fails if any event's timeout or a salvage statusMessage changes, and it would be shown wrong by a host that ignores statusMessage on plugin hooks
Frontmatter
| id | iss-323 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-323-hook-salvage-entries-invoke-bootstrap-sh.md
open on GitHub ↗ · commit history ↗
mentions11
- dcn-26080100000000072026-08-01 — itd-105 / spc-21, hook-ordering assumption DISCLOSED and defensively mitigated rather than verified. spc-21's Approach asserts "ordering within one event's hook list is preserved by the harness, so the binary-backed session hooks run after the bootstrap in the same event". That is an assumption about the harness, and nothing in this repository verifies it; if it is false, a fresh install's first session runs `abcd hook prompt-router-reset` and `abcd hook session-start` before any binary exists, and the intent's AC ("never a raw shell 'No such file or directory'") is missed by the two commands the bootstrap is supposed to protect. A PATH fallback is forbidden by the intent's Decisions and is NOT added. Instead both commands gain an inline existence check in `hooks/hooks.json` — the same shape as the `PreToolUse` guard entry's existing inline wrapper, which turns a bad exit code into a clean message — so if the assumption turns out to be false the failure is at least a plain-language message and a non-blocking exit 2 rather than a raw shell error, with no fallback binary resolution anywhere. This mitigates the SYMPTOM; the assumption itself still wants verifying against the real harness, and the maintainer should treat it as open. In the same file the bootstrap entry gains `"timeout": 240` (seconds): the three curl budgets plus the tag resolution total 180s worst case, which the harness's 60s default would have killed mid-run — which is precisely how the trap bug above and an orphaned temp dir would both have been reached in production. Deferred, deliberately, and bounded: two racers can both proceed when one breaks the other's ten-minute-stale lock, which last-write-wins resolves into a verified binary either way; and the trap is installed just after the lock is taken, leaving a signal window bounded by that same stale-lock recovery. Both are cheaper to state than to fix, and neither can install an unverified binary.
- dcn-26092900000000092026-09-29 — The product thinker's ruling of 2026-09-29 on the salvage-hook timeout is applied (iss-323; recorded by lane salvageTimeout of autonomous run A): every hook entry that runs `hooks/bootstrap.sh` as a salvage (`UserPromptSubmit`, `PreToolUse`, `PreCompact`; `SessionEnd` no longer does) declares `"timeout": 120`, and the wait is shown rather than silent through the host's `statusMessage` field, its spinner text while a hook runs. A notice from the script itself cannot show, because each salvage command sends the script's output to /dev/null and those command strings, which carry the PATH trust checks, stay byte-identical. `SessionStart` keeps 240, and `internal/surface/cli/hooks_timeout_test.go` pins every event's timeout, absent ones included. The host's hooks reference, read the same day, gives a command hook a 600-second default lowered to 30 on `UserPromptSubmit`, not the 60-second default the 2026-08-01 bootstrap entry records; so 120 raises the prompt hook's budget and bounds the other two below their default, which is the ruling's two-minute ceiling on any stall.
- dcn-26092900000000102026-09-29 — The product thinker's rulings CH1 and CH2 of 2026-09-29 correct the salvage-hook timeout entry above (iss-323; recorded by lane salvageTimeout of autonomous run A, fix round 1). CH1: only the every-message hook, `UserPromptSubmit`, declares `"timeout": 120`; `PreToolUse` and `PreCompact` declare no timeout and keep the host's ten-minute default, so a slow first download always finishes there. CH2: `SessionStart` (240 seconds) shows the same `statusMessage` as the salvage entries, and the text states no duration ("abcd: checking the plugin binary; a first run downloads it, so this can take a while"), because the entries that show it now have two-, four- and ten-minute limits. The command strings stay byte-identical, and `internal/surface/cli/hooks_timeout_test.go` pins both the timeouts and the message's lack of a duration. The entry above stands as written, since the ledger is append-only.
- dcn-26092900000000112026-09-29 — The product thinker answered the twenty-six Group J rulings (plan or close), J1 to J26 (recorded by lane recGroupJ of autonomous run A, for orchestrator abcd-fc). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, section "Group J", dated 2026-09-29, and are restated here because that file is not committed. PLAN: the installer script becomes a minimal starter with an ownership-checked hand-over, planned as a new intent with its planning interview owed (J2, iss-377, draft itd-2609292106557115); token metering and size classes are planned as one intent (J3, iss-2608301744251874 and iss-2608301856299268), and session token accounting in the history store joins that metering intent (J4, iss-2608220150157508; all three in draft itd-2609292107351737); the sources tooling moves into abcd's core with ingest and consult as abcd verbs (J5, iss-27, whose corpus half itd-76 already delivered), and the backfill of the placeholder source entries is part of that intent (J6, iss-55; both in draft itd-2609292108089653); the grill hands off to an installed external interview skill and falls back to its own (J11, iss-165, draft itd-2609292108373494); an opt-in local-model prompt sanitiser is planned (J14, iss-2608261543489261, draft itd-2609292109005937); decision-to-transcript links are planned, with the anchor kind and the link's home settled in the planning interview owed for draft itd-171 (J16, iss-2608290819228175); the dredge synthesis, its write-ups as their own memory source class, is planned together with draft itd-25, whose planning interview is owed (J19, iss-2609211905340006); one provenance register across ingest and vendoring is planned together with draft itd-26, whose planning interview is owed (J20, iss-2609211905346507); release retention, keeping the newest release per version line, is automated by planning draft itd-70 (J21, iss-282); a rules-backend seam with an opt-in CARL adapter is planned, the native loader staying the default (J22, iss-64, draft itd-2609292109214516); core-owned managed pre-commit gates are planned with draft itd-62 (J23, iss-84); a behavioural end-to-end scenario suite per verb family is planned (J24, iss-48, draft itd-2609292109475690). Every draft stays in drafts/ until a person plans it, and each source issue links its draft and names that planning interview in its deferral. BUILD: the teaching plane, the rules-loader safety domain generated from the hazard registry, is built as a lane, the registry being the single source (J10, iss-151 and itd-103; a code lane owns it). RULED DETAILS: `source_kind` carries both, as two separate labels, one for the tool and one for the route (J13, iss-2608230752354928); the prompt router's removal signal is the full list of active domains every time, a domain's absence meaning it has stopped (J15, iss-2608261550580260); setup offers an outside AI service (for example OpenRouter) at install, skippable, once the API adapter ships (J18, iss-2609081951416843, which waits on the API adapter); the salvage-hook timeout is 120 seconds, the wait must show a counter or a message while it runs and never stall silently, and every per-event timeout is pinned in a test (J25, iss-323). J13, J15 and J25 are carried by code lanes. CLOSE: the quick-tunnel preview protocol is closed unless the need recurs (J26, iss-2608230617385431, wontfix with that reason). PARKED ON A TRIGGER: agent payment protocols are kept under watch, not now, revisited as the protocols mature (J8, iss-138); the findings-only skill waits for the skill format to be versioned and stewarded (J9, iss-139); the disposition worksheet as an intent and a site page waits until after the first study (J17, iss-2609021815529020). DECIDE LATER: the Homebrew tap stays parked (J1, iss-380); pluggable search back ends (J7, iss-26); forge-backed record numbering (J12, iss-2608210737264758).
- dcn-26092900000000182026-09-29 — The product thinker's ruling of 2026-09-29 on the salvage-hook timeout is applied (iss-323; recorded by lane salvageTimeout of autonomous run A): every hook entry that runs `hooks/bootstrap.sh` as a salvage (`UserPromptSubmit`, `PreToolUse`, `PreCompact`; `SessionEnd` no longer does) declares `"timeout": 120`, and the wait is shown rather than silent through the host's `statusMessage` field, its spinner text while a hook runs. A notice from the script itself cannot show, because each salvage command sends the script's output to /dev/null and those command strings, which carry the PATH trust checks, stay byte-identical. `SessionStart` keeps 240, and `internal/surface/cli/hooks_timeout_test.go` pins every event's timeout, absent ones included. The host's hooks reference, read the same day, gives a command hook a 600-second default lowered to 30 on `UserPromptSubmit`, not the 60-second default the 2026-08-01 bootstrap entry records; so 120 raises the prompt hook's budget and bounds the other two below their default, which is the ruling's two-minute ceiling on any stall.
- dcn-26092900000000192026-09-29 — The product thinker's rulings CH1 and CH2 of 2026-09-29 correct the salvage-hook timeout entry above (iss-323; recorded by lane salvageTimeout of autonomous run A, fix round 1). CH1: only the every-message hook, `UserPromptSubmit`, declares `"timeout": 120`; `PreToolUse` and `PreCompact` declare no timeout and keep the host's ten-minute default, so a slow first download always finishes there. CH2: `SessionStart` (240 seconds) shows the same `statusMessage` as the salvage entries, and the text states no duration ("abcd: checking the plugin binary; a first run downloads it, so this can take a while"), because the entries that show it now have two-, four- and ten-minute limits. The command strings stay byte-identical, and `internal/surface/cli/hooks_timeout_test.go` pins both the timeouts and the message's lack of a duration. The entry above stands as written, since the ledger is append-only.
- dcn-26092900000000202026-09-29 — The product thinker answered the twenty-six Group J rulings (plan or close), J1 to J26 (recorded by lane recGroupJ of autonomous run A, for orchestrator abcd-fc). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, section "Group J", dated 2026-09-29, and are restated here because that file is not committed. PLAN: the installer script becomes a minimal starter with an ownership-checked hand-over, planned as a new intent with its planning interview owed (J2, iss-377, draft itd-2609292106557115); token metering and size classes are planned as one intent (J3, iss-2608301744251874 and iss-2608301856299268), and session token accounting in the history store joins that metering intent (J4, iss-2608220150157508; all three in draft itd-2609292107351737); the sources tooling moves into abcd's core with ingest and consult as abcd verbs (J5, iss-27, whose corpus half itd-76 already delivered), and the backfill of the placeholder source entries is part of that intent (J6, iss-55; both in draft itd-2609292108089653); the grill hands off to an installed external interview skill and falls back to its own (J11, iss-165, draft itd-2609292108373494); an opt-in local-model prompt sanitiser is planned (J14, iss-2608261543489261, draft itd-2609292109005937); decision-to-transcript links are planned, with the anchor kind and the link's home settled in the planning interview owed for draft itd-171 (J16, iss-2608290819228175); the dredge synthesis, its write-ups as their own memory source class, is planned together with draft itd-25, whose planning interview is owed (J19, iss-2609211905340006); one provenance register across ingest and vendoring is planned together with draft itd-26, whose planning interview is owed (J20, iss-2609211905346507); release retention, keeping the newest release per version line, is automated by planning draft itd-70 (J21, iss-282); a rules-backend seam with an opt-in CARL adapter is planned, the native loader staying the default (J22, iss-64, draft itd-2609292109214516); core-owned managed pre-commit gates are planned with draft itd-62 (J23, iss-84); a behavioural end-to-end scenario suite per verb family is planned (J24, iss-48, draft itd-2609292109475690). Every draft stays in drafts/ until a person plans it, and each source issue links its draft and names that planning interview in its deferral. BUILD: the teaching plane, the rules-loader safety domain generated from the hazard registry, is built as a lane, the registry being the single source (J10, iss-151 and itd-103; a code lane owns it). RULED DETAILS: `source_kind` carries both, as two separate labels, one for the tool and one for the route (J13, iss-2608230752354928); the prompt router's removal signal is the full list of active domains every time, a domain's absence meaning it has stopped (J15, iss-2608261550580260); setup offers an outside AI service (for example OpenRouter) at install, skippable, once the API adapter ships (J18, iss-2609081951416843, which waits on the API adapter); the salvage-hook timeout is 120 seconds, the wait must show a counter or a message while it runs and never stall silently, and every per-event timeout is pinned in a test (J25, iss-323). J13, J15 and J25 are carried by code lanes. CLOSE: the quick-tunnel preview protocol is closed unless the need recurs (J26, iss-2608230617385431, wontfix with that reason). PARKED ON A TRIGGER: agent payment protocols are kept under watch, not now, revisited as the protocols mature (J8, iss-138); the findings-only skill waits for the skill format to be versioned and stewarded (J9, iss-139); the disposition worksheet as an intent and a site page waits until after the first study (J17, iss-2609021815529020). DECIDE LATER: the Homebrew tap stays parked (J1, iss-380); pluggable search back ends (J7, iss-26); forge-backed record numbering (J12, iss-2608210737264758).
- iss-2609012039107700GHSA-gx3m-3224-qqcv (CWE-426, advisory severity medium): the hook shims' PATH fallback executes an unverified `abcd` as the guard and rules loader. In `hooks/hooks.json`, UserPromptSubmit, PreToolUse and PreCompact carry `if [ -z "$g" ] && command -v abcd; then g=abcd; fi` and SessionEnd `exec abcd hook session-end` when the plugin-root binary is missing; PreToolUse's fence passes exit 0/1/2 through, so a foreign binary exiting 0 approves every shell command. SessionStart has no PATH rung and fails closed. Reproduced at v0.7.0 with the shipped PreToolUse command, a failing bootstrap and a hijack directory first on PATH: the planted binary received `guard hook` plus the tool input and its exit 0 was accepted as armed. The rung was introduced by iss-254 (20968bdf, v0.5.1), is pinned as desired behaviour by `internal/surface/cli/hooks_selfprovision_test.go:TestBinaryHooksFallBackToAPathBinary`, and is a documented contract (docs/how-to/install.md: "the plugin root first, then an abcd on PATH … the install one-liner restores it"; brief 04-surfaces/01-ahoy.md section 9; brief 05-internals/03-configuration.md). The record contradicts itself: the 2026-08-01 decisions [dcn-2608010000000005](../../decisions/dcn-2608010000000005-itd-105-spc-21-implementation-the-plugin.md) and [dcn-2608010000000007](../../decisions/dcn-2608010000000007-itd-105-spc-21-hook-ordering-assumption.md) (itd-105 / spc-21) state "Rejected: a PATH fallback in the hook commands (spc-21 forbids it)" and "A PATH fallback is forbidden by the intent's Decisions and is NOT added"; iss-254 later added it with no DECISIONS entry reversing that.
- iss-2609302306281487Thirteen v0.12.0 docs-currency findings are deferred past the tag, all minor or nitpick: dc-3 (CHANGELOG.md:37 and RELEASE.md:35 call a role-word ban a bundled docs-lint rule; it is a pattern in this repository's .abcd/docs-lint.json), dc-4 (CHANGELOG.md:21 preamble vs the two breaking bullets filed under Added), dc-6 (commands/ahoy.md:23-24 a status argument the binary refuses), dc-8 (commands/ahoy.md:4 argument-hint omits credential), dc-10 (commands/capture.md:4 argument-hint omits mentions), dc-11 (commands/capture.md:633-635 widening_runs sits under reading_outstanding), dc-12 (commands/docs.md:4,75-118 omit docs fidelity --intent), dc-13 (commands/history.md:4 ingest hint omits the required --into), dc-14 (commands/implement.md:42-54 omit join --model and --reason), dc-16 (commands/ingest.md:4,119 no User input line for its argument-hint), dc-21 (commands/launch.md:4 argument-hint omits receipts), dc-22 (commands/launch.md:1035 names the project abcd-cli), dc-24 (resolved iss-323's resolution says all three salvage entries declare the 120-second timeout; only UserPromptSubmit does since 6a40c898b). Found by the v0.12.0 release-gate docs-currency review (Fable 5.1, tier full) over b89784c4.
- itd-105A fresh plugin install just works: the hook binary survives install and every update
- spc-21a-fresh-plugin-install-just-works-the-hook-binary-survives