Claude Code hook: set the marker in-process, and allow async for marker-only events

Open
#4,618 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
40/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
git, rust
Domain
cli, performance

Research direction

Locate the Claude Code hook handler in the Rust CLI and the plugin hooks.json that registers UserPromptSubmit, Stop, Notification, PreToolUse (AskUserQuestion) and PermissionRequest. Start by tracing the Stop path and the re-exec of wt -C <repo> config state marker set, then compare it with the repository discovery already done in-process. The change is done when marker writes reuse that discovery, the four marker-only events are marked async (PermissionRequest, WorktreeCreate and WorktreeRemove stay synchronous), and concurrent writes are ordered with a set_at timestamp and a short config.lock retry.

Written by the indexing model from the issue text.

Description

wt config plugins claude hook runs synchronously on UserPromptSubmit, Stop, Notification, PreToolUse (AskUserQuestion) and PermissionRequest, so Claude Code waits for it each time. In a repository with 88 worktrees on a Linux host, one Stop run takes about 140 ms. Over a week of sessions alongside other plugins' hooks, it was the slowest synchronous Stop hook at about half of turn ends.

Most of that time is process and git overhead, not the marker write. Averages of 10 untraced runs:

Step Time
Whole hook, Stop event 141 ms
The wt -C <repo> config state marker set 💬 it re-execs 94 ms
git worktree list alone 31 ms
The git config marker write alone 21 ms

An strace -f -e trace=execve of one Stop run shows the sequence. The hook process discovers the repository with git --version, git rev-parse ... and git config --list -z. It then re-execs wt -C <repo> config state marker set, which runs the same three git calls again. The child also runs git worktree list --porcelain -z and git rev-parse --git-dir twice before the git config write.

Suggestions:

  1. Set the marker in the hook process instead of re-execing wt. Reusing the discovery the process has already done removes about 47 ms per run. The child's git worktree list and duplicate rev-parse --git-dir look unneeded for writing one branch's marker. Skipping them as well should bring a run close to one discovery plus the 21 ms write. That second part is inferred, not measured.

  2. Mark the marker-only registrations "async": true in the plugin's hooks.json: UserPromptSubmit, Notification, PreToolUse (AskUserQuestion) and Stop. Claude Code 2.1.310 backgrounds an async command hook about 17 ms after spawning it. It drops the hook's decisions and plain-text output. These events print nothing on stdout, so nothing is lost. PermissionRequest has to stay synchronous, because it answers the EnterWorktree permission prompt. WorktreeCreate and WorktreeRemove also stay synchronous, because Claude Code uses their output and exit status.

    Async needs ordered marker writes first. Today Claude Code waits for the Stop write before it starts a queued prompt's hooks. With async, a prompt queued at turn end can start its 🤖 write while the Stop's 💬 write is still running, and whichever finishes last wins. That leaves the wrong emoji until the next event. Writes that start together also contend on config.lock: in a scratch probe, 40 simultaneous starts gave 30 lock failures. One way to make the end state independent of completion order:

    • stamp set_at in milliseconds when the hook starts;
    • skip the write when the stored set_at is newer;
    • retry briefly when config.lock is held.

Seen on worktrunk wt v0.81.0.

This was written by Claude Code on behalf of max-sixty

Dominant language
Rust
Stars
8.8k
Forks
315
Avg merge
1d 6h
Merged PRs (30d)
204

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from max-sixty/worktrunk

All issues in max-sixty/worktrunk

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.