pbhandover is a single npm CLI that keeps one shared HANDOVER.md — a living project handover note — automatically up to date for both Claude Code and Codex CLI. It works through each agent's Stop hook, and it is non-blocking: when a turn ends the hook only enqueues a tiny job and returns immediately. A detached background worker then asks an agent (in headless mode) to summarize the latest turn into HANDOVER.md. Your agent never waits on the handover update.
日本語版は README.ja.md を参照してください。
- Repository: https://github.com/hiroshi-tamura/pbhandover
- License: MIT
pbhandover unifies and supersedes two earlier tools —
pbClaudeHooksHandoverandpbCodexHooksHandover. Those tools are deprecated and will be removed. See Migrating from the predecessor tools.
- What problem it solves
- Highlights
- Requirements
- Installation
- Quickstart
- Daily use:
@handoverinside a session - Command reference
- How it works (architecture)
- Configuration
- Environment variables
- Agent integration details
- Migrating from the predecessor tools
- Uninstall
- FAQ & troubleshooting
- Development
- Privacy & Git
When an agent session gets long, the next turn — or the next agent, or the next human — needs a short, current handover note: what the project is, what is being worked on, what commands were run, what succeeded, what failed, what is suspected, what was tried, what to do next, and what should not be touched.
pbhandover automates that note and keeps it shared across agents:
- An agent finishes a turn and its
Stopevent fires. - The Stop hook quickly writes one queued job and returns (non-blocking).
- A detached background worker processes queued jobs one at a time.
- For each job, the worker runs the same agent that produced the turn in headless mode to refresh the single shared
HANDOVER.mdfrom a template. - Because
HANDOVER.mdis shared, a handover continues seamlessly whether your last turn was in Claude Code or in Codex.
The generated handover files are local by default. They can include machine-specific details, so the tool excludes them from Git unless you explicitly opt in.
- One shared state directory per project —
.pbhandover/holds config, template, prompt, the queue, logs, and the worker lock. This unifies the old.pbclaude-handover/.pbcodex-handoversplit. - One shared
HANDOVER.mdat the project root, written and read by both agents. - Per-firing-agent summarization — each Stop hook tags its job with which agent fired (
--agent claude/--agent codex). The worker summarizes that job with the same agent (Claude turns by Claude, Codex turns by Codex). Models are configurable per agent. - In-session control — type
@handover on|off|status|flush(alsosetup/doctor) directly inside a Claude Code or Codex session. Claude Code also exposes a native/handoverslash command. - Non-blocking — the Stop hook just enqueues; the worker runs detached in the background.
- Recursion-safe — guards prevent the worker's own agent run from triggering an endless handover loop.
- Secret redaction — API keys, tokens, secrets, passwords,
Bearertokens, andsk-...keys are stripped from queued payloads, transcripts, and captured output.
- Node.js 18 or later
- npm
- The agent(s) you want to use:
- Claude Code CLI (
claudeon yourPATH) — for Claude integration - Codex CLI (
codexon yourPATH) — for Codex integration
- Claude Code CLI (
- Windows, macOS, or Linux
You do not need both agents. pbhandover on auto-detects which agents are present and enables only those.
On Windows, run commands in PowerShell or Command Prompt. On macOS/Linux, use Terminal, bash, or zsh.
Global install from the npm registry:
npm install -g pbhandoverA package postinstall script runs pbhandover setup --quiet on a best-effort basis (it never fails the install). setup creates the shared user template/prompt/config and installs the @handover command router for every agent that is available on your machine.
Global installation does not automatically enable handover generation for all projects. You still enable each project separately with pbhandover on (or @handover on inside a session).
-
Open a terminal and
cdinto the project where you wantHANDOVER.mdmaintained. -
Enable handover for the project:
pbhandover on
With no agent flags this auto-detects agents: it enables every agent whose project dir (
.claude/.codex) exists, or whose CLI is installed. If none is detected, it falls back to enabling all known agents. -
Check the setup:
pbhandover status
-
Work normally in Claude Code and/or Codex. Each time an agent finishes a turn, the Stop hook queues a handover update and the background worker refreshes
HANDOVER.md. -
Before closing the project or handing it off, drain any pending jobs:
pbhandover flush
You can also do all of this from inside an agent session — see below.
This is the primary, everyday way to drive the tool. Inside a Claude Code or Codex session, type:
@handover on
@handover off
@handover status
@handover flush
@handover setup and @handover doctor are also accepted (doctor is an alias of status).
How it works: a UserPromptSubmit router intercepts prompts that start with @handover (also /handover, handover, or pbhandover), runs the matching CLI subcommand locally in the project, and returns the result back to the session without sending it to the model as a normal prompt.
- In Claude Code, the router responds with a
blockdecision, so@handover ...never costs model tokens. Claude Code additionally supports the native/handoverslash command (/handover on,/handover status, etc.), which runs the same CLI through the Bash tool. - In Codex, the router runs the command locally and injects the result as additional context, asking Codex to report the result instead of treating the prompt as an ordinary request.
Only this fixed set of subcommands is routed: on, off, status, doctor, flush, setup. Anything else is passed through to the model normally.
| Command | What it does |
|---|---|
pbhandover setup [--force] [--no-router] |
Create the shared user template.md, prompt.md, and config.json, and install the @handover command router (+ /handover slash command) for every available agent. --force overwrites the shared template/prompt with package defaults. --no-router skips router installation. |
pbhandover on [--claude] [--codex] [--track-handover] [--track-hooks] [--force-template] |
Enable the Stop hook in the current project. No agent flag → auto-detect (see Quickstart). Creates .pbhandover/, the project template/prompt/config, HANDOVER.md, and each selected agent's Stop hook. Updates .gitignore. |
pbhandover off [--claude] [--codex] |
Disable this tool's Stop hook in the current project. No flag → all currently configured agents. Leaves HANDOVER.md and .pbhandover/ in place. |
pbhandover status (alias doctor) |
Show global ON/OFF, the handover/template paths, queue counts (pending/done/failed), and per-agent enabled / model / available / hook-file status. |
pbhandover router install|uninstall|status [--claude] [--codex] |
Manage the @handover UserPromptSubmit router and the /handover slash command. No flag → available agents for install/uninstall; status always reports all agents. |
pbhandover trust |
Trust this tool's installed Codex hooks (writes [hooks.state.*] entries into ~/.codex/config.toml). |
pbhandover enqueue --agent claude|codex |
Internal Stop hook entrypoint. Writes one queued job and spawns the worker, then returns. You normally do not run this manually. |
pbhandover worker |
Internal. Process queued jobs sequentially in the background. |
pbhandover flush |
Process queued jobs in the foreground and wait until the queue drains. |
pbhandover template path|sync [--force] |
path prints the shared and project template paths. sync creates the project template from the shared one (--force overwrites). |
pbhandover --help / -h |
Show usage. |
pbhandover --version / -V |
Print the version. |
--claude/--codex— enable only the named agent(s). With neither flag, agents are auto-detected.--track-handover— keepHANDOVER.mdout of.gitignore(by default it is ignored).--track-hooks— keep the agent hook files out of.gitignore.--force-template— overwrite the project-local template and prompt from the shared user copies.
User-level (shared by every project on your machine) — created by setup:
- Shared dir:
~/.pbhandover/(Windows:%USERPROFILE%\.pbhandover\)template.md— default handover structure for new projectsprompt.md— default worker writing policyconfig.json— default queue mode, handover filename, redaction flag, per-agent default modelsprompt-router.log— log of routed@handovercommands
- Claude user settings:
~/.claude/settings.json(router hook) — base dir overridable viaCLAUDE_CONFIG_DIR - Claude slash command:
~/.claude/commands/handover.md - Codex user hooks:
~/.codex/hooks.json(router hook) — base dir overridable viaCODEX_HOME
Project-level (created in the current project by on):
.pbhandover/— the shared, agent-neutral state dir (see layout below).claude/settings.local.json— Claude Stop hook entry (when Claude is enabled).codex/hooks.json— Codex Stop hook entry (when Codex is enabled)HANDOVER.md— the shared handover note at the project root
The user-level template is the starting point; each project gets its own copy, so editing one project's template does not affect other projects.
your-project/
HANDOVER.md # shared handover note (project root)
.claude/
settings.local.json # Claude Stop hook (personal; git-ignored by default)
.codex/
hooks.json # Codex Stop hook (git-ignored by default)
.pbhandover/
config.json # project config (enabled, agents, models, tracking flags)
template.md # project-local handover structure
prompt.md # project-local worker writing policy
queue/ # pending jobs (one JSON per queued turn)
done/ # completed job records
failed/ # failed job records (for inspection)
worker.log # background worker log
worker.lock # single-worker lock (stale after 30 min)
last-prompt.md # last prompt sent to a summarizer
last-claude-message.txt # last message captured from Claude
last-codex-message.txt # last message captured from Codex
Note:
last-prompt.md,last-claude-message.txt, andlast-codex-message.txtare written by the worker as it processes jobs, so they appear once a handover update has run.
Agent finishes a turn (Claude Code or Codex)
│
▼
Stop hook fires ──► pbhandover enqueue --agent <claude|codex>
│ • recursion guards check (see below)
│ • writes ONE job into .pbhandover/queue/
│ • spawns a detached background worker
▼ • returns immediately (NON-BLOCKING)
Agent is free to continue — no waiting
── meanwhile, in the background ───────────────────────────────
pbhandover worker
│ • takes the single worker.lock
│ • for each queued job, oldest first:
▼
buildPrompt(): prompt rules + template + existing HANDOVER.md
+ redacted job + redacted transcript tail
│
▼
runSummarizer() with the SAME agent that fired the hook
• claude: claude -p --model <model> --output-format text
• codex: codex exec --model <model> --output-last-message ...
(worker sets PBHANDOVER_WORKER=1 so the agent's own Stop
hook is a no-op — this breaks the recursion)
│
▼
Agent edits HANDOVER.md in place (writes to disk, not stdout)
│
▼
success → job moved to done/ failure → job moved to failed/
│
▼
repeat until the queue is empty, then release the lock
Each Stop hook tags its job with the agent that fired it (--agent claude or --agent codex). When the worker processes a job, it summarizes that turn with the same agent, so the model that produced the work also writes the handover:
| Agent | Default summarizer model | Headless invocation |
|---|---|---|
| Claude Code | claude-haiku-4-5-20251001 |
claude -p --model <model> --dangerously-skip-permissions --output-format text (summary captured from stdout) |
| Codex CLI | gpt-5.3-codex-spark |
codex --disable hooks --model <model> --sandbox workspace-write --ask-for-approval never exec --skip-git-repo-check --output-last-message <file> - |
Models are configurable per agent in .pbhandover/config.json (see Configuration).
The worker spawns an agent in headless mode, and that agent fires its own Stop hook when it finishes — which could enqueue another handover job forever. Two guards prevent the loop:
- The worker sets the environment variable
PBHANDOVER_WORKER=1. Theenqueuecommand returns immediately whenever it sees this, so the summarizer's Stop hook does nothing. enqueuealso returns immediately when the agent's hook payload containsstop_hook_active: true(the agents mark re-entrant Stop events).
Before anything is written to the queue, used in a prompt, or captured to disk, text is passed through a redaction filter that masks:
api_key/api-key/apikey,token,secret,password/passwd, andauthorizationvalues →[REDACTED]sk-...style keys (20+ chars) →sk-[REDACTED]Bearer <token>→Bearer [REDACTED]
Redaction covers queued payloads, the transcript tail, the existing HANDOVER.md excerpt fed to the summarizer, and captured stdout/stderr. It is a safety net, not a guarantee — review generated files before sharing.
1. Package default: templates/default-template.md prompts/default-prompt.md
2. User shared : ~/.pbhandover/template.md ~/.pbhandover/prompt.md (created by setup)
3. Project-local : .pbhandover/template.md .pbhandover/prompt.md (copied by on)
4. HANDOVER.md created from the project-local template
When the worker builds a prompt it reads, in priority order: the project-local template → falling back to the bundled default. Because the template is read per job, editing .pbhandover/template.md changes the next handover update. Edit the project-local template when one project needs a different format; edit the user-level shared template to change the default for future projects.
The default template is in Japanese and has ten sections (project purpose; current work/tasks; commands run; successes/completed tasks; failures/errors; suspected causes; attempted fixes; next actions/backlog; cautions; other notable items), plus a "last updated" line and an "agent" field that records which agent (claude or codex) produced the update.
Created by setup:
{
"queueMode": "sequential",
"handoverFile": "HANDOVER.md",
"redactSecrets": true,
"agents": {
"claude": { "enabled": false, "model": "claude-haiku-4-5-20251001" },
"codex": { "enabled": false, "model": "gpt-5.3-codex-spark" }
}
}Created/updated by on:
{
"enabled": true,
"trackHandover": false,
"trackHooks": false,
"queueMode": "sequential",
"handoverFile": "HANDOVER.md",
"templateFile": ".pbhandover/template.md",
"promptFile": ".pbhandover/prompt.md",
"agents": {
"claude": { "enabled": true, "model": "claude-haiku-4-5-20251001" },
"codex": { "enabled": true, "model": "gpt-5.3-codex-spark" }
}
}enabled— whether the project has handover enabled at all (true if any agent is enabled).agents.<name>.enabled— per-agent on/off.agents.<name>.model— the model used to summarize that agent's turns. Change it to any model id/alias the agent accepts.trackHandover/trackHooks— whetherHANDOVER.mdand the hook files are kept out of.gitignore.workerTimeoutMs(optional) — per-job summarizer timeout in milliseconds. Defaults to600000(10 minutes) when unset.
| Variable | Effect |
|---|---|
PBHANDOVER_WORKER=1 |
Set by the worker around the summarizer run. When present, enqueue is a no-op. This is the primary recursion guard — you normally do not set it yourself. |
PBHANDOVER_NO_WORKER=1 |
enqueue writes the job but does not spawn the background worker. Useful for tests or when you want to control worker runs manually with flush. |
PBHANDOVER_CLAUDE_BIN |
Override the claude binary used for summarization. |
PBHANDOVER_CODEX_BIN |
Override the codex binary used for summarization. |
CLAUDE_CONFIG_DIR |
Override Claude's home dir (default ~/.claude); affects where the router hook and slash command are installed. |
CODEX_HOME |
Override Codex's home dir (default ~/.codex); affects where the router hook and config.toml trust entries are written. |
- Project Stop hook — added to
.claude/settings.local.jsonunderhooks.Stop, running... enqueue --agent claude. - User router — a
hooks.UserPromptSubmitentry in~/.claude/settings.jsonrunning... prompt-router --agent claude. - Slash command —
~/.claude/commands/handover.mdis installed so/handover ...works natively.
- Project Stop hook — added to
.codex/hooks.jsonunderhooks.Stop, running... enqueue --agent codex. - User router — a
hooks.UserPromptSubmitentry in~/.codex/hooks.jsonrunning... prompt-router --agent codex. - Hook trust — Codex requires hooks to be trusted.
onautomatically trusts pbhandover's hooks (viapostEnable), and you can re-run it any time withpbhandover trust. Trust works by querying the Codexapp-serverfor the installed hooks and writing[hooks.state.<key>]blocks (withtrusted_hashandenabled = true) into~/.codex/config.toml. Runtrustagain after reinstalling/moving the package or editing the generated hook commands.
pbhandover supersedes pbClaudeHooksHandover and pbCodexHooksHandover. Those two are deprecated and will be removed — new projects should use pbhandover.
Key differences when migrating:
- The split state folders
.pbclaude-handoverand.pbcodex-handoverare replaced by a single unified.pbhandover/. - There is now one shared
HANDOVER.mdfor both agents instead of two separate flows.
To migrate a project:
- Disable the old tool's hooks using its own commands (e.g. the predecessor's
offandrouter uninstall), and remove its old hook entries. - Run
pbhandover onafresh in the project. This creates the unified.pbhandover/layout and installs the new Stop hooks. - Optionally delete the now-unused
.pbclaude-handover/and.pbcodex-handover/folders after confirming you no longer need their history.
cd path/to/your/project
pbhandover offThis removes this tool's Stop hook entries but leaves .pbhandover/ and HANDOVER.md for review.
After confirming you no longer need them, delete HANDOVER.md and .pbhandover/, and remove the pbhandover Stop entries from .claude/settings.local.json and/or .codex/hooks.json (delete those files entirely only if they hold no other settings you use).
pbhandover router uninstallnpm uninstall -g pbhandoverOnly after uninstalling, remove the shared dir if it is no longer needed:
- Windows:
%USERPROFILE%\.pbhandover - macOS/Linux:
~/.pbhandover
pbhandover is not found. Ensure npm's global bin is on your PATH (npm bin -g / npm config get prefix), then restart the terminal.
@handover on is treated like a normal prompt. Install/refresh the router with pbhandover router install (for Codex also run pbhandover trust), then restart the agent in that project.
HANDOVER.md is not updating. Run pbhandover status to inspect queue counts, then pbhandover flush to process pending jobs in the foreground. Inspect .pbhandover/worker.log and the .pbhandover/failed/ directory for errors.
A handover update never ran (queue stays pending). The detached worker may have failed to launch — check .pbhandover/worker.log. You can always drain the queue manually with pbhandover flush. If you intentionally set PBHANDOVER_NO_WORKER=1, jobs only run via flush/worker.
I changed the template but the file still looks old. Run pbhandover flush. The worker reads the project template per job, so the next processed job uses the new format.
"A worker is already running." Only one worker runs at a time, guarded by .pbhandover/worker.lock. A stale lock (older than 30 minutes) is reclaimed automatically. If needed, delete worker.lock manually after confirming no worker is active.
Codex says the hook is untrusted. Run pbhandover trust (and restart Codex). This rewrites the [hooks.state.*] trust entries in ~/.codex/config.toml.
Which agent summarizes which turn? The same agent that produced the turn. Claude turns are summarized by Claude, Codex turns by Codex — each with its configured model.
npm install
npm test # node --test
npm run smoke # pbhandover --help
node bin/pbhandover.js --helpBy default, on adds the following to .gitignore:
/HANDOVER.md
.pbhandover/
.claude/settings.local.json # when Claude is enabled
.codex/hooks.json # when Codex is enabledHANDOVER.md is anchored with a leading / so it only matches the repo-root file (important on case-insensitive filesystems). Use --track-handover / --track-hooks to keep those files tracked.
Keep secrets out of handover files, queue files, prompts, templates, and logs: API keys, tokens, passwords, .env contents, private keys, internal server names, and personal or customer data. Payloads, transcript tails, and captured output are passed through a redaction filter, but always review generated files before sharing a repository, issue reproduction, or support bundle.