Skip to content

Bound _hook_hm_event's hm call with a short timeout#41

Merged
cgraf78 merged 2 commits into
mainfrom
hm-hook-timeout
Jul 3, 2026
Merged

Bound _hook_hm_event's hm call with a short timeout#41
cgraf78 merged 2 commits into
mainfrom
hm-hook-timeout

Conversation

@cgraf78

@cgraf78 cgraf78 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • hm's canonical store for a given alias can be a network-backed mount (an rclone/cloud-synced Google Drive path, for one real machine) with no latency bound of its own; _hook_hm_event called hm fully unguarded, relying entirely on the host agent's own outer hook timeout to eventually kill it
  • traced a real "UserPromptSubmit hook timed out after 10s" complaint back to exactly this, compounded by an unrelated hook-array duplication bug in the calling dotfiles repo (fixed separately, over there)
  • adds _hook_timeout_prefix, a general-purpose portable timeout/gtimeout wrapper (no-ops when neither is present), guards the hm call with it (default 2s via AGENTGUARD_HIVE_MEMORY_TIMEOUT), distinguishes a timeout warning from a hard failure, and still returns success either way since this is advisory infrastructure, not a reason to fail the calling hook
  • consolidates agent-hook-session-end-claude's own inline duplicate of the same timeout/gtimeout pattern onto the new shared helper

Test plan

  • full suite (test/agentguard-test): all suites, 0 failed
  • shellcheck and checkrun lint clean on all changed files
  • new tests: a real hung mock hm is cut off near budget, timeout vs hard-failure messaging is distinct, a timed-out call still returns success, the default (2s, env var unset) is locked in by its own test, and _hook_timeout_prefix is tested directly for both guarded and no-op-fallback cases

🤖 Generated with Claude Code

https://claude.ai/code/session_011jWSryYVwQnNc6vsaWXd1J

cgraf78 and others added 2 commits July 3, 2026 09:41
Summary

`hm`'s canonical store for a given alias can be a network-backed mount
(an rclone/cloud-synced Google Drive path, for one real machine) with
no latency bound of its own. `_hook_hm_event` called `hm` unguarded,
relying entirely on the host agent's own outer hook timeout to
eventually kill it — a real incident traced a "UserPromptSubmit hook
timed out after 10s" complaint back to exactly this, compounded by an
unrelated hook-array duplication bug in the calling dotfiles repo.

- add `_hook_timeout_prefix`, a general-purpose (not Hive Memory
  specific) portable `timeout`/`gtimeout` wrapper that no-ops when
  neither binary is present, matching the platform-support level
  this repo already targets (macOS ships neither by default)
- guard the `hm` call in `_hook_hm_event` with it, defaulting to 2s
  via `AGENTGUARD_HIVE_MEMORY_TIMEOUT` — normal warm calls measured
  around 60-130ms, so 2s is generous headroom while still keeping the
  worst case tight and well inside any host agent's own hook timeout
- a `rc -eq 124` (the guard firing) now warns distinctly from a hard
  `hm` failure, so "the store is slow" and "the store is broken" are
  distinguishable in hook stderr/warnings
- either way, `_hook_hm_event` still returns 0: the timeout is
  advisory infrastructure, not a reason to fail the calling hook —
  the caller just proceeds without memory context that turn
- consolidate `agent-hook-session-end-claude`'s own inline
  timeout/gtimeout duplication (second use of the same four-line
  pattern) onto the new shared helper

Testing

- full suite (`test/agentguard-test`): all suites, 0 failed
- `shellcheck` and `checkrun lint` clean on all changed files
- new tests: a real hung mock `hm` (not just a mocked exit code) is
  cut off near the configured budget rather than left to run, the
  timeout is reported distinctly from a hard failure, a timed-out
  call still returns success to the caller, the *default* (env var
  unset) is locked at 2s so a future change to the hardcoded default
  fails this test instead of silently drifting, and `_hook_timeout_prefix`
  itself is tested directly for both the guarded and no-op-fallback
  cases

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jWSryYVwQnNc6vsaWXd1J
Summary

CI caught a real gap in the previous commit: GitHub's macOS runners
ship neither `timeout` nor `gtimeout`, so `_HOOK_TIMEOUT_PREFIX` fell
back to empty and the guard did nothing — three tests failed after
running the mock's full 10s unguarded instead of being cut off near
the configured budget. This isn't just a CI quirk; stock macOS (no
Homebrew coreutils) has the same gap, so any Mac in the fleet without
coreutils installed would have been just as unprotected as before this
whole change.

- add a portable bash-only timeout implementation (background the
  command, poll every 0.1s, TERM then KILL past budget, exit 124) used
  only when neither real binary is available, so the guard actually
  works everywhere bash runs instead of depending on an
  undocumented/optional dependency
- it has to be a real executable, not a shell function: `env -u
  HIVE_MEMORY_PROJECT ...` (used on one of _hook_hm_event's two code
  paths) can only exec real binaries, not the calling shell's
  functions, so the fallback is a `bash -c '<script>' _ "$seconds"`
  invocation instead — no new dependency since bash is already
  required
- fix the test suite's own mock `hm hang` case to `exec sleep` instead
  of forking it: a plain `sleep 10` child left an orphaned grandchild
  holding a captured-output pipe open for the full 10s even after the
  timeout guard correctly fired at the configured budget, which is a
  test-mock artifact (the real hm binary is a single process, not a
  shell wrapping a subprocess) that masked the fallback actually
  working correctly end to end

Testing

- full suite (`test/agentguard-test`): all suites, 0 failed
- `shellcheck` and `checkrun lint` clean on all changed files
- new tests: the portable fallback itself is proven against a real
  hanging `sleep`, and a second end-to-end test goes through the real
  `_hook_hm_prompt_submit` path with only bash/dirname/basename/ps/sed
  /tr/cat/jq/mktemp/sleep on PATH (everything hook-helpers.sh and
  detect.sh need, minus timeout/gtimeout specifically) against a
  genuinely hung mock `hm` — the closest local reproduction of the
  exact scenario that failed on macOS CI

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jWSryYVwQnNc6vsaWXd1J
@cgraf78 cgraf78 merged commit 268ab00 into main Jul 3, 2026
6 checks passed
@cgraf78 cgraf78 deleted the hm-hook-timeout branch July 3, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant