Skip to content

perf(hooks): fire-and-forget telemetry hooks (#573) - #688

Merged
rohitg00 merged 3 commits into
mainfrom
perf/hooks-fire-and-forget
May 27, 2026
Merged

perf(hooks): fire-and-forget telemetry hooks (#573)#688
rohitg00 merged 3 commits into
mainfrom
perf/hooks-fire-and-forget

Conversation

@rohitg00

@rohitg00 rohitg00 commented May 27, 2026

Copy link
Copy Markdown
Owner

Summary

Telemetry hooks block Claude Code's next-prompt boundary on every assistant turn because they await fetch(...) inside a try/catch — the hook process stays alive until the response arrives, up to the AbortSignal.timeout duration per request. On a slow daemon or local hang this stacks visibly into the UX.

Switch the 9 telemetry-only hooks to fire-and-forget:

fetch(url, { signal: AbortSignal.timeout(N) }).catch(() => {});
setTimeout(() => process.exit(0), 500).unref();

The unawaited fetch dispatches the request; the unref'd setTimeout force-exits the process after the request has been flushed to the local daemon's socket buffer (~500ms is enough). Without the setTimeout Node keeps the event loop alive waiting for any in-flight fetch to settle, which means the hook still blocks Claude Code's next-prompt boundary for up to the AbortSignal duration — exactly the bug fire-and-forget is meant to fix.

Changes

Telemetry hooks switched to fire-and-forget (9 files):

  • notification, post-tool-failure, post-tool-use, prompt-submit
  • subagent-start, subagent-stop, task-completed
  • stop, session-end (multi-fetch branches all converted)

Context-injecting hooks left untouched — they read the response to write context to stdout, so they must await fetch:

  • pre-tool-use, pre-compact, session-start

AGENTS.md updated with the two-pattern guidance (context-injecting vs telemetry-only) so the convention is discoverable for future hooks.

Test plan

  • npx vitest run — 1238 pass, 1 integration file skipped (server not running, pre-existing)
  • Build emits fire-and-forget shape — grep setTimeout plugin/scripts/notification.mjs confirms setTimeout(() => process.exit(0), 500).unref(); lands in the bundle
  • No await left in telemetry hook bundles — grep -E 'await fetch' plugin/scripts/{notification,post-tool-use,post-tool-failure,prompt-submit,subagent-start,subagent-stop,task-completed,stop,session-end}.mjs returns empty
  • Build emits no hashed _project*.mjs chunks (per-entry tsdown config from fix(hooks): send repo basename as project, not full path (#474) #687 holds)

Stacked on top of #687 (the fix/474 reimplementation) since both PRs heavily touch the same hook files. GitHub will auto-reconcile base to main once #687 lands.

Closes #573.

Summary by CodeRabbit

  • Refactor
    • Hook and reporting scripts now send telemetry/observations in a non-blocking way and schedule a short, guaranteed process shutdown.
    • Preserves existing payloads and behavior but avoids waiting for network calls, reducing hang-time during agent transitions.
    • Result: faster, more consistent shutdowns and reduced delays when stopping or ending sessions.

Review Change Stack

@vercel

vercel Bot commented May 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentmemory Ready Ready Preview, Comment May 27, 2026 7:48pm

Request Review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e3b9ff4-abbe-4010-b957-2b2cd053cc70

📥 Commits

Reviewing files that changed from the base of the PR and between 0dea5b6 and 04e2223.

📒 Files selected for processing (5)
  • AGENTS.md
  • plugin/scripts/session-end.mjs
  • plugin/scripts/stop.mjs
  • src/hooks/session-end.ts
  • src/hooks/stop.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • AGENTS.md
  • plugin/scripts/stop.mjs
  • src/hooks/session-end.ts
  • src/hooks/stop.ts
  • plugin/scripts/session-end.mjs

📝 Walkthrough

Walkthrough

Hook and CLI scripts were changed to send telemetry as non-blocking fire-and-forget fetch calls (errors suppressed via .catch) and to schedule short unref'd process exits, with AGENTS.md updated to document context-injecting vs telemetry-only hook patterns.

Changes

Fire-and-forget telemetry hook patterns

Layer / File(s) Summary
Fire-and-forget pattern documentation
AGENTS.md
AGENTS.md now specifies two patterns: context-injecting hooks must await timed fetch inside try/catch; telemetry-only hooks must dispatch unawaited timed fetch and force exit with an unref'd short timer.
Single-request telemetry hooks (500ms exit)
src/hooks/notification.ts, plugin/scripts/notification.mjs, src/hooks/post-tool-failure.ts, plugin/scripts/post-tool-failure.mjs, src/hooks/post-tool-use.ts, plugin/scripts/post-tool-use.mjs, src/hooks/prompt-submit.ts, plugin/scripts/prompt-submit.mjs, src/hooks/task-completed.ts, plugin/scripts/task-completed.mjs, src/hooks/subagent-stop.ts, plugin/scripts/subagent-stop.mjs
Replaced awaited fetch (try/catch) with non-blocking fetch(...).catch(() => {}) and added setTimeout(() => process.exit(0), 500).unref() to terminate shortly after dispatch.
Multi-request shutdown hooks (1500ms exit)
src/hooks/session-end.ts, plugin/scripts/session-end.mjs, src/hooks/stop.ts, plugin/scripts/stop.mjs
Session-end and stop now dispatch multiple best-effort fetch calls without awaiting, suppress errors via .catch(), and schedule a longer setTimeout(() => process.exit(0), 1500).unref() after initiating requests.
Subagent start exit safeguard
src/hooks/subagent-start.ts, plugin/scripts/subagent-start.mjs
Added an unref'd setTimeout(process.exit, 500) so the subagent-start hook process exits even if pending handles would otherwise keep the event loop alive.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • rohitg00/agentmemory#579: Modifies stop hook logic around /agentmemory/session/end; closely related to this PR's stop hook refactor.

Poem

🐰 I bounced through hooks at break of day,

Fired off the mems and hopped away.
No waiting, no linger, a quick little dash —
The process exits fast with a hopeful splash.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'perf(hooks): fire-and-forget telemetry hooks' directly and accurately summarizes the main change: converting telemetry hooks to use fire-and-forget pattern instead of awaited fetches.
Linked Issues check ✅ Passed The pull request fulfills all coding requirements from #573: converts nine telemetry hooks to fire-and-forget pattern with unref'd setTimeout exit timers, preserves context-injecting hooks unchanged, updates AGENTS.md with pattern guidance, and fixes tool_response payload bug.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the #573 objectives: updates to nine telemetry hook files, AGENTS.md documentation, and one payload fix in post-tool-use. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/hooks-fire-and-forget

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rohitg00
rohitg00 force-pushed the perf/hooks-fire-and-forget branch from 42a9df9 to a1ca8d6 Compare May 27, 2026 18:57
Base automatically changed from fix/474-hook-project-basename to main May 27, 2026 19:32
rohitg00 added 2 commits May 27, 2026 20:34
Telemetry hooks (notification, post-tool-failure, post-tool-use,
prompt-submit, stop, session-end, subagent-start, subagent-stop,
task-completed) previously `await fetch(..., AbortSignal.timeout(N))`
inside a try/catch. The await kept the hook process alive until the
response arrived — up to N ms per request — which blocks Claude Code's
next-prompt boundary on every assistant turn.

Switch to fire-and-forget:

  fetch(url, { signal: AbortSignal.timeout(N) }).catch(() => {});
  setTimeout(() => process.exit(0), 500).unref();

The unawaited fetch dispatches the request; the unref'd setTimeout
force-exits the process after the request has been flushed to the
local daemon's socket buffer (~500ms is enough). Without the
setTimeout Node keeps the event loop alive waiting for any in-flight
fetch to settle, which means the hook still blocks Claude Code's
next-prompt boundary for up to the AbortSignal duration.

Context-injecting hooks (pre-tool-use, pre-compact, session-start)
still use `await fetch` because Claude Code reads their stdout for
context injection — left untouched.

AGENTS.md updated with the two-pattern guidance.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/scripts/stop.mjs`:
- Around line 26-38: The exit delay is too short for the multi-request hook:
update the setTimeout that calls process.exit(0) (the call paired with the two
fetches to `${REST_URL}/agentmemory/summarize` and
`${REST_URL}/agentmemory/session/end`) to use 1500 ms instead of 500 ms so both
fetch requests have time to be initiated; keep the .unref() call to allow clean
shutdown but extend the timeout to 1500 to match the PR guidance for
multi-request session-end hooks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0ba3b3b-ed8a-4fdf-afc4-eaaf3c4d2364

📥 Commits

Reviewing files that changed from the base of the PR and between 0468407 and 0dea5b6.

📒 Files selected for processing (19)
  • AGENTS.md
  • plugin/scripts/notification.mjs
  • plugin/scripts/post-tool-failure.mjs
  • plugin/scripts/post-tool-use.mjs
  • plugin/scripts/prompt-submit.mjs
  • plugin/scripts/session-end.mjs
  • plugin/scripts/stop.mjs
  • plugin/scripts/subagent-start.mjs
  • plugin/scripts/subagent-stop.mjs
  • plugin/scripts/task-completed.mjs
  • src/hooks/notification.ts
  • src/hooks/post-tool-failure.ts
  • src/hooks/post-tool-use.ts
  • src/hooks/prompt-submit.ts
  • src/hooks/session-end.ts
  • src/hooks/stop.ts
  • src/hooks/subagent-start.ts
  • src/hooks/subagent-stop.ts
  • src/hooks/task-completed.ts

Comment thread plugin/scripts/stop.mjs Outdated
Multi-request hooks (stop fires 2, session-end up to 4) need more
than 500ms to initiate all fetches when AGENTMEMORY_URL points to a
remote daemon — DNS + TCP + TLS handshakes can eat the budget before
the second/third fetch is even dispatched. Bump to 1500ms on those
two hooks only; single-request hooks keep 500ms.

AGENTS.md updated with the multi-request exception.
@rohitg00
rohitg00 merged commit d626b4e into main May 27, 2026
7 checks passed
@rohitg00
rohitg00 deleted the perf/hooks-fire-and-forget branch May 27, 2026 19:51
This was referenced May 28, 2026
rohitg00 added a commit that referenced this pull request May 28, 2026
Bumps version across 9 files + adds CHANGELOG entry summarizing the
18 commits since v0.9.22.

Highlights:
- GitHub Copilot CLI first-class support (#534) — plugin + hooks +
  MCP with LSP-style Content-Length framing on the standalone stdio
  transport.
- Five new MCP adapters: Warp, Cline, Continue, Zed, Droid (#677);
  ADAPTERS count 11 → 17.
- Three silent DX bugs fixed: graph extraction never fired on
  session end (#666 / #698), status reported zero memories (#666),
  consolidation defaulted off even with an LLM provider configured
  (#612 / #696).
- Nine telemetry hooks switched to fire-and-forget so they don't
  block Claude Code's next-prompt boundary (#573 / #688).
- Hook project field now sends repo basename instead of full
  filesystem path so auto-injected context isn't silently filtered
  out (#474 / #687).
- Local-LLM docs: Ollama / LM Studio / vLLM section added (#671 /
  #697).

Version-bump files: package.json, plugin/.claude-plugin/plugin.json,
plugin/plugin.json, plugin/.codex-plugin/plugin.json,
packages/mcp/package.json, src/version.ts, src/types.ts,
src/functions/export-import.ts, test/export-import.test.ts.
rohitg00 added a commit that referenced this pull request May 28, 2026
* chore(release): v0.9.23

Bumps version across 9 files + adds CHANGELOG entry summarizing the
18 commits since v0.9.22.

Highlights:
- GitHub Copilot CLI first-class support (#534) — plugin + hooks +
  MCP with LSP-style Content-Length framing on the standalone stdio
  transport.
- Five new MCP adapters: Warp, Cline, Continue, Zed, Droid (#677);
  ADAPTERS count 11 → 17.
- Three silent DX bugs fixed: graph extraction never fired on
  session end (#666 / #698), status reported zero memories (#666),
  consolidation defaulted off even with an LLM provider configured
  (#612 / #696).
- Nine telemetry hooks switched to fire-and-forget so they don't
  block Claude Code's next-prompt boundary (#573 / #688).
- Hook project field now sends repo basename instead of full
  filesystem path so auto-injected context isn't silently filtered
  out (#474 / #687).
- Local-LLM docs: Ollama / LM Studio / vLLM section added (#671 /
  #697).

Version-bump files: package.json, plugin/.claude-plugin/plugin.json,
plugin/plugin.json, plugin/.codex-plugin/plugin.json,
packages/mcp/package.json, src/version.ts, src/types.ts,
src/functions/export-import.ts, test/export-import.test.ts.

* chore(release): add #701 + #709 to v0.9.23 CHANGELOG
healdigital pushed a commit to healdigital/agentmemory that referenced this pull request Jul 27, 2026
…#688)

* perf(hooks): fire-and-forget telemetry hooks (closes rohitg00#573)

Telemetry hooks (notification, post-tool-failure, post-tool-use,
prompt-submit, stop, session-end, subagent-start, subagent-stop,
task-completed) previously `await fetch(..., AbortSignal.timeout(N))`
inside a try/catch. The await kept the hook process alive until the
response arrived — up to N ms per request — which blocks Claude Code's
next-prompt boundary on every assistant turn.

Switch to fire-and-forget:

  fetch(url, { signal: AbortSignal.timeout(N) }).catch(() => {});
  setTimeout(() => process.exit(0), 500).unref();

The unawaited fetch dispatches the request; the unref'd setTimeout
force-exits the process after the request has been flushed to the
local daemon's socket buffer (~500ms is enough). Without the
setTimeout Node keeps the event loop alive waiting for any in-flight
fetch to settle, which means the hook still blocks Claude Code's
next-prompt boundary for up to the AbortSignal duration.

Context-injecting hooks (pre-tool-use, pre-compact, session-start)
still use `await fetch` because Claude Code reads their stdout for
context injection — left untouched.

AGENTS.md updated with the two-pattern guidance.

* chore(hooks): drop verbose comments on fire-and-forget hooks

* fix(hooks): bump stop+session-end exit delay to 1500ms

Multi-request hooks (stop fires 2, session-end up to 4) need more
than 500ms to initiate all fetches when AGENTMEMORY_URL points to a
remote daemon — DNS + TCP + TLS handshakes can eat the budget before
the second/third fetch is even dispatched. Bump to 1500ms on those
two hooks only; single-request hooks keep 500ms.

AGENTS.md updated with the multi-request exception.
healdigital pushed a commit to healdigital/agentmemory that referenced this pull request Jul 27, 2026
* chore(release): v0.9.23

Bumps version across 9 files + adds CHANGELOG entry summarizing the
18 commits since v0.9.22.

Highlights:
- GitHub Copilot CLI first-class support (rohitg00#534) — plugin + hooks +
  MCP with LSP-style Content-Length framing on the standalone stdio
  transport.
- Five new MCP adapters: Warp, Cline, Continue, Zed, Droid (rohitg00#677);
  ADAPTERS count 11 → 17.
- Three silent DX bugs fixed: graph extraction never fired on
  session end (rohitg00#666 / rohitg00#698), status reported zero memories (rohitg00#666),
  consolidation defaulted off even with an LLM provider configured
  (rohitg00#612 / rohitg00#696).
- Nine telemetry hooks switched to fire-and-forget so they don't
  block Claude Code's next-prompt boundary (rohitg00#573 / rohitg00#688).
- Hook project field now sends repo basename instead of full
  filesystem path so auto-injected context isn't silently filtered
  out (rohitg00#474 / rohitg00#687).
- Local-LLM docs: Ollama / LM Studio / vLLM section added (rohitg00#671 /
  rohitg00#697).

Version-bump files: package.json, plugin/.claude-plugin/plugin.json,
plugin/plugin.json, plugin/.codex-plugin/plugin.json,
packages/mcp/package.json, src/version.ts, src/types.ts,
src/functions/export-import.ts, test/export-import.test.ts.

* chore(release): add rohitg00#701 + rohitg00#709 to v0.9.23 CHANGELOG
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