Skip to content

Fix hooks for current Claude Code, show permission details, update dependencies - #93

Closed
jeckyl2010 wants to merge 15 commits into
patoles:mainfrom
jeckyl2010:contrib/hooks-and-updates
Closed

jeckyl2010 wants to merge 15 commits into
patoles:mainfrom
jeckyl2010:contrib/hooks-and-updates

Conversation

@jeckyl2010

Copy link
Copy Markdown

Fixes for current Claude Code (2.1.288), permission details in the UI, async hooks, and dependency updates. Each commit stands on its own and passes CI, so this can be split into smaller PRs if you prefer.

Fixes

The hook fails when a package.json above it says "type": "module". Node picks a .js file's module type from the nearest package.json. A ~/package.json with "type": "module" (as bun init leaves in a home directory) makes ~/.claude/agent-flow/hook.js load as an ES module, where require is undefined, so every hook fails with ReferenceError: require is not defined in ES module scope and no events reach Agent Flow.

  • The hook is now hook.mjs, written with import: Node always loads it as an ES module, whatever lies above it.
  • Existing installs move over on their own: the extension's migration on activation rewrites hook.js commands, setup.js treats them as not set up, and the uninstaller removes both.

A failed tool shows no reason. Claude Code sends it as error on PostToolUseFailure, not as tool_response, so every failure showed an empty [FAILED]. It now reads error (falling back to tool_response for older versions), marks the call as an error in the UI, and shows an interrupt as [INTERRUPTED].

Features

What a permission waits for. A new PermissionRequest hook gives the agent that asked, and the tool and command it waits on. The agent's detail card shows it, and the timeline block reads e.g. "Permission: Bash". Once a session sends it, the generic permission Notification is skipped; that remains the fallback for Claude Code versions without the event. Repeated reports of one wait keep one timeline block.

Async hooks. Hooks run with async: true, so Claude Code never waits for the forwarder. A fast tool's end can then arrive before its start, so the hook server tracks tool_use_id: an early end gets a start shown first, and the late start is dropped. Installs whose hooks lack an event or async are configured again.

Updates

  • Latest minor versions: next 16.3, react 19.3, vite 8.3, tailwindcss 4.3, postcss, tsx and the React types; esbuild 0.28.
  • TypeScript 7. The extension's tsconfig names node and vscode in types, as TypeScript 7 no longer includes every installed @types package. next build still type-checks under it (a planted type error fails the build).
  • Vite 8.3 warns about configs its future native loader won't accept: web/ is now "type": "module", and its configs use file extensions and import.meta.dirname. The app build no longer warns; the webview build still does, as it imports the extension's constants, which are CommonJS.
  • packageManager: pnpm@10.34.6: pnpm switches to it inside the repo, so a newer global pnpm can't rewrite the lockfile in a format CI's refuses. CI's pnpm/action-setup now reads the version from it; naming it in both places is an error.
  • app/build.js runs Vite directly instead of through npx, which under pnpm warned about pnpm's settings in the environment.

Left out on purpose: @types/vscode and @types/node stay matched to the oldest supported VS Code and its Node; concurrently 10 and the GitHub Actions major versions are better as their own changes.

Testing

  • CI's checks (root tests, extension tests, extension typecheck, web typecheck) pass at every commit; every build (extension, webview, app, web) passes at the tip, from a clean pnpm install --frozen-lockfile.
  • The hook server, run against out-of-order tool events and a permission request: every start and end arrives in order, and the permission once, with its command.
  • In a home directory with a "type": "module" package.json and hook.js hooks: setup.js moved them to hook.mjs with async on all ten events, kept the user's own hooks, and the installed hook forwarded a real event. A second run changed nothing.
  • In daily use with Claude Code 2.1.288 through the standalone app.

Node loads a .js file by the nearest package.json above it, so a
"type": "module" one in the home directory (such as bun init leaves)
made hook.js load as an ES module, where require is undefined, and every
hook failed. As hook.mjs with import, Node loads it as an ES module
whatever lies above it.

Installs running hook.js move to hook.mjs on their own: the extension's
migration on activation now also rewrites those commands, setup.js counts
them as not set up, and the uninstaller removes both.
Claude Code sends a failed tool's reason as `error` on
PostToolUseFailure, not as `tool_response`, so every failure showed
as an empty [FAILED]. Read `error`, falling back to `tool_response`
for older versions; mark the call as an error in the UI, and show an
interrupt as [INTERRUPTED] rather than a failure.
* PermissionRequest: the agent that asked, and the tool and command it
  waits on, in its detail card and on the timeline block. Once a session
  sends it, the generic permission Notification is skipped; it remains
  the fallback for Claude Code versions without the event.
* Hooks run with async: true, so Claude Code never waits on the
  forwarder. A fast tool's end can now arrive before its start: the hook
  server tracks tool_use_id, shows a start for an early end, and drops
  the late one.
* Existing installs are configured again when an event or async is
  missing from their hooks.
* Repeated permission reports for one wait keep one timeline block, and
  a wait on an agent not on the canvas shows on the main agent.
* next 16.3, react 19.3, vite 8.3, tailwindcss 4.3, postcss, tsx and
  the React types
* esbuild 0.28, for the extension's and the app's builds

Every build and CI check passes; major updates are left for their own
changes.
TypeScript 7 no longer includes every installed @types package by
default, so the extension names the two it uses. The web build still
type-checks under it: a planted type error fails next build.
Its Vite configs are ES modules; Vite 8.3 warns that a package without
"type": "module" loads them as CommonJS. Nothing in web/ is CommonJS.
Imports name their file extension, and paths come from
import.meta.dirname rather than __dirname, which ES modules lack. The
app build no longer warns; the webview build still does, as it imports
the extension's constants, which are CommonJS.
pnpm reads it and switches to that version inside the repo, so a newer
global pnpm can't rewrite the lockfile in a format CI's pnpm refuses.
CI's pnpm/action-setup now takes the version from it too: naming it in
both places is an error.
npx under pnpm warned about pnpm's settings in the environment, which
npm doesn't know. The build now runs the web package's own Vite with
the current Node: no npm and no shell.
@cla-assistant

cla-assistant Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@cla-assistant

cla-assistant Bot commented Oct 3, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

The published app sent anonymous usage telemetry unless it was turned off.
It now sends nothing, and writes nothing to ~/.agent-flow, unless
AGENT_FLOW_TELEMETRY=true is set; DO_NOT_TRACK=1 still wins over it.
plugin/ is a Claude Code mod that sends a session's events to a running
Agent Flow in the hook payload shape its hook server already reads, found
through the same discovery files. From inside Claude Code it reports what the
settings.json hooks and the transcript can only infer: a tool call's start and
end from one wrapped call, a permission request only when its dialog shows,
subagents by their real ids under the agent that started them, each model
request's model and usage, the session's cost, and answers and thinking as
they stream.

For a session the mod reports, the hook server drops the command hooks'
copies of its events, and the transcript side leaves tool calls, subagents,
answers and the permission guess to the mod. The hook server also sends each
model request as a model_step event with the agent's usage totals, and stops
treating a subagent's stop as a return to itself.
… answers

For agents the bridge mod reports, the canvas now shows what the API measured
rather than estimates:

- Cost: each model request priced from its usage at its own model's rates,
  which now differ by version within a family and price cache reads apart.
  The session total is Claude Code's own; a figure that is partly estimated
  is marked with ~.
- Cache: a ring around each agent for the share of its latest request the
  prompt cache served, and in the cost panel the overall share and what the
  cache saved.
- Heartbeat: every model request pulses out from its agent, sized by what it
  wrote and colored by why it stopped.
- Streaming: answers and thinking type into their bubble as they arrive.

The context ring and bar fill to the measured total, a measured count is no
longer raised by tool result estimates, and an attached image's note no
longer names the main agent.
The bridge mod now reports the effort each model request ran with. Under
each agent, a chip names its current model with one hex pip per effort level,
low to max. When the model or effort changes, the chip decodes into its new
value with a sweep across it and a hexagonal shockwave from the node, and the
change is marked on the agent's timeline row.

Heartbeat pulses are now hexagons like the nodes, echoed once per effort
level above medium.
A finished agent keeps its model tag while its node fades, so a subagent
that runs for a few seconds still shows what it ran on. The cache share
moves from a label beside the ring, which collided with the cards and
bubbles around small subagent nodes, into the agent's model tag. Claude
Code's SubagentHandback call, its plumbing for returning a subagent's answer,
is no longer drawn as a tool call.
@jeckyl2010 jeckyl2010 closed this Oct 4, 2026
@jeckyl2010
jeckyl2010 deleted the contrib/hooks-and-updates branch October 4, 2026 10:26
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