npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lioneltay/worker-manager

v0.0.5

Published

Claude Code plugin for spawning and managing worker agents in isolated worktrees

Readme

@lioneltay/worker-manager

A Claude Code plugin that spawns autonomous worker agents in isolated git worktrees. Workers run in tmux sessions with their own Claude Code instance and communicate back to the orchestrator via a file-based mail system.

Installation

Install via the Claude Code plugin marketplace, or manually:

npm install -g @lioneltay/worker-manager

Quick start

Once the plugin is installed, the orchestrator tools are available in your Claude Code session:

> Spawn a worker to refactor the auth module into separate files

# Claude calls start_worker({ title: "refactor-auth", task: "..." })
# Worker is created in a new worktree on branch worker/refactor-auth-a3f1b2c0
# Worker runs autonomously in tmux session worker-a3f1b2c0

> [next prompt — hook fires automatically]
# "Worker refactor-auth (a3f1b2c0) COMPLETED: Refactored auth module into..."

How it works

A single Node.js binary (dist/index.js) serves four different roles depending on how it's invoked:

flowchart TD
    Entry["node dist/index.js"]
    Entry --> CheckHook{"--hook flag?"}
    CheckHook -->|Yes| Hook["UserPromptSubmit Hook
    Read & display pending mail"]
    CheckHook -->|No| CheckStop{"--stop-hook flag?"}
    CheckStop -->|Yes| StopHook["Stop Hook
    Block premature stops"]
    CheckStop -->|No| CheckWorker{"WORKER_ID env var?"}
    CheckWorker -->|Yes| Worker["Worker MCP Server
    Tools: complete, ask"]
    CheckWorker -->|No| Orchestrator["Orchestrator MCP Server
    Tools: start_worker, list_workers,
    nudge_worker, stop_worker, read_mail"]

The orchestrator runs as an MCP server in the lead agent's Claude Code session. When it spawns a worker, it launches a new Claude Code instance in a tmux session, configured with a worker MCP server that provides complete and ask tools. Workers communicate back via the filesystem — no HTTP servers, no daemons.

Tools

Orchestrator tools

| Tool | Input | Description | | -------------- | --------------------------------------------- | --------------------------------------------------------------------------------- | | start_worker | title, task, useWorktree, baseBranch? | Spawn a worker agent in an isolated worktree or the current directory. | | list_workers | — | List all workers with status. Cross-references tmux to detect crashed workers. | | nudge_worker | id, message | Send a message to a worker's tmux session (answers questions, provides guidance). | | stop_worker | id | Stop a running worker by killing its tmux session. Worktrees are preserved. | | read_mail | — | Read and clear all pending messages from this orchestrator's workers. |

Worker tools

| Tool | Input | Description | | ---------- | ---------- | ----------------------------------------------------------------------------------- | | complete | summary | Signal task completion. Writes a mail message and updates the registry. | | ask | question | Ask the orchestrator a question. Sets status to "asking" and writes a mail message. |

Worker lifecycle

sequenceDiagram
    participant User
    participant O as Orchestrator
    participant FS as State (filesystem)
    participant T as tmux
    participant W as Worker (Claude Code)
    participant WT as Git Worktree

    User->>O: "spawn a worker to do X"
    O->>WT: Create worktree (new branch)
    O->>FS: Register worker in state.json
    O->>T: Create tmux session
    O->>T: Launch: claude --dangerously-skip-permissions<br/>with worker MCP + task prompt
    T->>W: Claude Code starts

    Note over W: Works autonomously<br/>in isolated worktree

    alt Task completed
        W->>FS: complete(summary)<br/>Write mail + update status
    else Needs clarification
        W->>FS: ask(question)<br/>Write mail + update status
    end

    Note over User,O: On next prompt submission...
    FS-->>O: UserPromptSubmit hook<br/>reads and prints mail

    alt Worker asked a question
        O->>T: nudge_worker(id, message)<br/>tmux send-keys
        T->>W: Message appears as user input
    end

    Note over User,O: When orchestrator tries to stop...
    FS-->>O: Stop hook checks for unread mail<br/>blocks if messages are pending

State management

All state is stored in a temp directory derived from the git root, keeping the repository clean:

$TMPDIR/worker-manager/<hash>/
├── state.json                    # Worker registry
├── orchestrators/
│   └── <claude-code-pid>         # Maps PID → orchestrator ID
└── mail/
    ├── <orchestrator-id>/        # Per-orchestrator mailbox
    │   ├── 2025-01-15T...-uuid.json
    │   └── 2025-01-15T...-uuid.json
    └── <orchestrator-id>/
        └── ...

The <hash> is the first 12 characters of the SHA-256 of the git root path. This means different repositories get isolated state, and worktrees of the same repository share state with the main checkout.

Worker registry (state.json)

Tracks all workers spawned from this git root:

{
  "workers": {
    "a3f1b2c0": {
      "id": "a3f1b2c0",
      "name": "refactor-auth",
      "task": "Refactor the auth module...",
      "status": "running",
      "branch": "worker/refactor-auth-a3f1b2c0",
      "worktreePath": "/repo/.worktrees/worker--refactor-auth-a3f1b2c0",
      "tmuxSession": "worker-a3f1b2c0",
      "createdAt": "2025-01-15T10:30:00.000Z",
      "useWorktree": true
    }
  }
}

Registry writes are atomic — data is written to a temp file first, then renamed into place via fs.renameSync.

Worker statuses

| Status | Meaning | | ----------- | ------------------------------------------------------------- | | running | Worker is actively processing its task | | completed | Worker called complete — task is done | | asking | Worker called ask — waiting for orchestrator response | | failed | Detected by list_workers when tmux session no longer exists | | stopped | Worker was explicitly stopped or cleaned up on shutdown |

Mail messages

Each message is a separate JSON file named <timestamp>-<uuid>.json:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "workerId": "a3f1b2c0",
  "workerName": "refactor-auth",
  "type": "completion",
  "content": "Refactored auth module into login.ts, register.ts, and middleware.ts",
  "timestamp": "2025-01-15T10:35:00.000Z"
}

Message types: completion, question.

Reading mail is destructive — files are deleted after being read. This ensures each message is delivered exactly once.

Multi-session isolation

Multiple Claude Code sessions in the same directory each get their own mailbox:

flowchart TB
    subgraph "Claude Code Session A (PID 1234)"
        MCP_A["Orchestrator MCP<br/>id: abc12345"]
        Hook_A["Hooks<br/>ppid: 1234"]
    end

    subgraph "Claude Code Session B (PID 5678)"
        MCP_B["Orchestrator MCP<br/>id: def67890"]
        Hook_B["Hooks<br/>ppid: 5678"]
    end

    subgraph "State Directory"
        Mapping["orchestrators/<br/>1234 → abc12345<br/>5678 → def67890"]
        Mail_A["mail/abc12345/<br/>messages..."]
        Mail_B["mail/def67890/<br/>messages..."]
    end

    MCP_A -->|"writes mapping"| Mapping
    MCP_B -->|"writes mapping"| Mapping
    Hook_A -->|"reads ppid 1234<br/>→ abc12345"| Mapping
    Hook_B -->|"reads ppid 5678<br/>→ def67890"| Mapping
    Hook_A -->|"reads only"| Mail_A
    Hook_B -->|"reads only"| Mail_B

How it works:

  1. When the orchestrator MCP server starts, it generates a unique ID and writes a mapping from its parent PID (the Claude Code process) to that ID.
  2. Workers receive the orchestrator ID via the ORCHESTRATOR_ID env var and write mail to mail/<orchestratorId>/.
  3. Hooks run as children of the same Claude Code process, so process.ppid matches the MCP server's process.ppid. They look up the mapping to find the right mailbox.

This ensures each session only reads its own workers' mail, even when multiple sessions share the same git root.

Hooks

The plugin registers two Claude Code hooks:

UserPromptSubmit hook

Fires before every prompt. Reads pending mail from this orchestrator's mailbox and prints it to stdout, where Claude Code displays it as context for the next turn.

[Worker refactor-auth (a3f1b2c0)] COMPLETED: Refactored auth module into...

Stop hook

Fires when Claude Code is about to stop. Has two modes depending on context:

Orchestrator mode (no WORKER_ID env var): Checks for unread mail. If mail is pending, blocks the stop with a JSON decision so the orchestrator processes worker results before going idle.

Worker mode (WORKER_ID env var set): Checks whether the worker called complete or ask. If not, blocks the stop and reminds the worker to call one of those tools. This prevents workers from silently exiting without reporting results.

flowchart TD
    StopHook["Stop Hook Fires"]
    StopHook --> IsWorker{"WORKER_ID set?"}

    IsWorker -->|No| OrchestratorPath["Orchestrator Mode"]
    OrchestratorPath --> HasMapping{"Orchestrator ID<br/>mapping exists?"}
    HasMapping -->|No| AllowStop0["Allow stop<br/>(no workers spawned)"]
    HasMapping -->|Yes| HasMail{"Pending mail?"}
    HasMail -->|No| AllowStop1["Allow stop"]
    HasMail -->|Yes| BlockOrch["Block stop<br/>Show mail content"]

    IsWorker -->|Yes| WorkerPath["Worker Mode"]
    WorkerPath --> CheckStatus{"Worker status?"}
    CheckStatus -->|"completed / asking"| AllowStop2["Allow stop"]
    CheckStatus -->|running| CheckRetries{"Retries >= 2?"}
    CheckRetries -->|Yes| AllowStop3["Allow stop<br/>(give up)"]
    CheckRetries -->|No| BlockWorker["Block stop<br/>'Call complete or ask'"]

The worker stop hook has a retry limit of 2 to prevent infinite loops — if the worker ignores the reminder twice, it's allowed to exit.

Worker spawning

When start_worker is called, the following happens:

sequenceDiagram
    participant O as Orchestrator
    participant WT as Worktree Manager
    participant FS as Filesystem
    participant T as tmux

    O->>WT: create("worker/name-id", { newBranch: true })
    WT-->>O: { path: "/repo/.worktrees/worker--name-id" }

    O->>FS: Write worker to state.json
    O->>FS: Write MCP config to $TMPDIR/mcp-config-{id}.json

    Note over FS: MCP config points worker at same<br/>binary with env vars: WORKER_ID,<br/>WORKER_NAME, STATE_DIR, ORCHESTRATOR_ID

    O->>T: tmux new-session -d -s worker-{id}
    O->>T: tmux send-keys:<br/>export WORKER_ID=... STATE_DIR=...<br/>claude --dangerously-skip-permissions<br/>--mcp-config {path} --session-id {uuid}<br/>--append-system-prompt "..." "task"

Key details:

  • Same binary, different mode: The worker's MCP config points at the same dist/index.js. The WORKER_ID env var causes it to start as a worker MCP server instead of an orchestrator.
  • Environment variables in tmux: WORKER_ID and STATE_DIR are exported as shell variables in the tmux session before launching Claude. This allows the Stop hook (which inherits the shell environment) to detect that it's running in a worker context.
  • --dangerously-skip-permissions: Workers run without permission prompts since they're autonomous.
  • System prompt injection: Workers are told to call complete when done or ask if blocked.

Cleanup

Explicit stop

Use stop_worker to kill a specific worker's tmux session. The worker's status is updated to "stopped" and its worktree is preserved on disk for merging.

Shutdown cleanup

When the orchestrator MCP server exits (Claude Code session ends, SIGTERM, or SIGINT), all running workers are automatically cleaned up:

  1. All workers with status: "running" have their tmux sessions killed
  2. Their status is updated to "stopped" in the registry
  3. The orchestrator's PID mapping file is removed

Worktrees are intentionally preserved in both cases — they contain code changes the user may want to merge.

Worktree vs shared directory

useWorktree: true creates an isolated git worktree via @lioneltay/worktree-manager. useWorktree: false runs the worker in the current directory on the current branch — useful for parallel tasks that don't need branch isolation (e.g., research, testing).

Source files

src/
├── index.ts          # Entry point — mode detection + hook implementations
├── orchestrator.ts   # Orchestrator MCP server (start_worker, list_workers, etc.)
├── worker.ts         # Worker MCP server (complete, ask)
├── spawn.ts          # Worker spawning logic (tmux + Claude CLI)
├── state.ts          # File-based state (registry, mail, orchestrator mapping)
└── types.ts          # Shared type definitions

Plugin structure

.claude-plugin/
  plugin.json         # Plugin manifest
.mcp.json             # MCP server configuration
hooks/
  hooks.json          # Hook definitions (UserPromptSubmit + Stop)

Dependencies

  • @modelcontextprotocol/sdk — MCP server implementation
  • @lioneltay/worktree-manager — Git worktree creation
  • zod — Input validation for MCP tool schemas
  • tmux — Required system dependency for worker session management