Skip to content
Back to skills

Session Processes

ASecurity

ShipIt session worker processes: Claude CLI spawning and NDJSON parsing, agent abstraction (AgentProcess, ProxyAgentProcess), Docker Compose services (ServiceManager), file watcher, terminal PTY, session worker endpoints. Load when working on Claude process management, compose services, file watcher, terminal, or agent adapters.

  • 10 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 3, 2026
developmenttypescriptgoshellnodedockergitapi

Works with

  • terminal
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add nikzlabs/shipit --skill session-processes --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Session Processes?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Session Processes
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nikzlabs-session-processes/badge)](https://www.skillsdirectory.com/skills/nikzlabs-session-processes)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: session-processes
description: "ShipIt session worker processes: Claude CLI spawning and NDJSON parsing, agent abstraction (AgentProcess, ProxyAgentProcess), Docker Compose services (ServiceManager), file watcher, terminal PTY, session worker endpoints. Load when working on Claude process management, compose services, file watcher, terminal, or agent adapters."
user-invocable: true
---

# Session Worker Processes

Each session runs in a Docker container with a Fastify server (port 9100) that manages the agent CLI, file watcher, and terminal. **Preview/dev servers are not among them** — they moved to Docker Compose under the orchestrator's `ServiceManager`. The orchestrator communicates with session workers via HTTP (commands) and SSE (events).

## Session Worker

`src/server/session/session-worker.ts` — Fastify server running inside each container.

### Endpoints

| Method | Path | Purpose |
|--------|------|---------|
| `POST` | `/agent/start` | Start Claude with prompt, images, system prompt |
| `POST` | `/agent/interrupt` | Graceful interrupt (Ctrl+C) |
| `POST` | `/agent/kill` | Force terminate |
| `POST` | `/agent/stdin` | Write to agent stdin (answer questions) |
| `POST` | `/terminal/start` | Start interactive shell |
| `POST` | `/terminal/input` | Write to terminal |
| `POST` | `/terminal/resize` | Resize terminal |
| `POST` | `/files/watch` | Start file watcher (idempotent) |
| `POST` | `/files/unwatch` | Stop file watcher |
| `GET` | `/files/tree` | Scan file tree |
| `GET` | `/events` | SSE event stream |
| `GET` | `/health` | Health check |

### SSE Event Stream

The `/events` endpoint is a long-lived SSE connection. The worker broadcasts events from all managed processes:

| Event | Source | Data |
|-------|--------|------|
| `agent_event` | Claude CLI | Parsed NDJSON agent events |
| `agent_done` | Claude CLI | Exit code |
| `agent_error` | Claude CLI | Error message |
| `agent_auth_required` | Claude CLI | Auth URL |
| `agent_log` | Claude CLI | Non-JSON output lines |
| `terminal_data` | Terminal | Output data |
| `terminal_exit` | Terminal | Exit code |
| `file_changes` | FileWatcher | Changed file paths |

On SSE connect (or reconnect), the worker replays current state:
- If terminal is alive -> sends empty `terminal_data` signal

Note: Preview/dev servers are now managed by Docker Compose via `ServiceManager` in the orchestrator, not inside the session worker. See `service-manager.ts` and `compose-generator.ts`.

## Claude Process

`src/server/session/agents/claude/process.ts` — spawns the Claude CLI and manages streaming NDJSON interaction. (docs/155 split the agent code into per-agent directories: `agents/claude/`, `agents/codex/`.)

### Spawning

Spawned with `child_process.spawn("claude", args)` using `--output-format stream-json`, `--permission-mode`
(`plan` or `auto`) and `--allowedTools`. The tool list comes from `AUTO_TOOL_SPEC` / `PLAN_TOOL_SPEC` plus
`mcp__<server>__*` globs for user MCP servers (plan mode excludes third-party MCP tools); read `process.ts` for the current args.

### NDJSON Parsing

Claude CLI outputs one JSON object per line. The process:
1. Accumulates PTY data into a line buffer
2. On each newline, attempts `JSON.parse()`
3. Valid JSON -> emit as `"event"` (typed as `ClaudeEvent`)
4. Invalid JSON -> emit as `"log"` (non-event output)

### Watchdog

A 30-second inactivity timer detects hung processes. If no output is received within the timeout, it logs a warning. This handles edge cases where the CLI process stalls.

### Prompt Delivery

Prompts are written to stdin as JSON content blocks:
```json
[{"type":"text","text":"user prompt"},{"type":"image","source":{"type":"base64","media_type":"image/png","data":"..."}}]
```

File context references are prepended as text blocks with XML-like markers.

### Teardown kills the whole tree

Every ShipIt-initiated termination of an agent CLI — turn end, interrupt escalation, runner dispose — goes through `killProcessTree` (`shared/kill-child.ts`), never a bare `killChild` on the CLI's pid. An agent CLI is the root of a tree: MCP servers below it, and below those whatever they spawn. A pid-only kill leaves those running, reparented to the container's pid 1, until the container dies — one production Codex turn left a Playwright Chromium burning half a core for 19 minutes after it ended.

A process-group kill is **not** the equivalent: `playwright-core` spawns every browser `detached`, in a session of its own, so a group signal never reaches it. The helper snapshots descendants from `/proc` *before* the first signal (once the CLI dies, its grandchildren are orphaned and unfindable), signals the root through `killChild`, signals the snapshot, then SIGKILLs survivors after a 5s grace. It walks a tree only when `/proc` says the root is our own child, and identity-checks `starttime` before every delayed signal.

SIGINT-first paths (`ClaudeProcess.interrupt`, the OpenCode and Grok interrupts) still signal the CLI alone so it can flush; they reach the tree teardown through their existing escalation to `kill()`. Detail: `docs/289-agent-process-tree-teardown`.

## Agent Abstraction

`src/server/session/agents/claude/adapter.ts` wraps `ClaudeProcess` as an `AgentProcess` (defined in `src/server/shared/types/agent-types.ts`). The adapter:

- Translates `ClaudeEvent` -> `AgentEvent` (unified event format)
- Reports capabilities (supported tools, models, permission modes)
- Forwards all events from the inner process

The `AgentProcess` interface is transport-agnostic:
```typescript
interface AgentProcess extends EventEmitter {
  agentId: AgentId;
  capabilities: AgentCapabilities;
  run(params: AgentRunParams): void;
  writeStdin(data: string): void;
  interrupt(): void;
  kill(): void;
}
```

In production, the orchestrator never creates agents directly. Instead, `ContainerSessionRunner.createAgent()` returns a `ProxyAgentProcess` that delegates to the worker via HTTP:
- `run()` -> `POST /agent/start`
- `interrupt()` -> `POST /agent/interrupt`
- `kill()` -> `POST /agent/kill`
- `writeStdin()` -> `POST /agent/stdin`

Agent events flow back via SSE -> proxied to the `ProxyAgentProcess` event emitter.

## Docker Compose Services

Dev servers and other services are managed by Docker Compose, not the session worker. The orchestrator runs:

- `ServiceManager` (`src/server/orchestrator/service-manager.ts`) — per-session compose lifecycle: start/stop, status polling, log streaming, IP resolution
- `compose-generator.ts` — generates override files with ShipIt labels, session network, volume rewrites, port stripping

Services are defined in `docker-compose.yml` at the workspace root. `shipit.yaml` references the compose file and configures the agent container (install commands, resource limits).

The agent container can query service status/logs via the orchestrator API using `SHIPIT_HOST`, `SHIPIT_PORT`, and `SHIPIT_SESSION_ID` environment variables.

## File Watcher

`src/server/session/file-watcher.ts` — uses `fs.watch` with `recursive: true`.

- 300ms debounce to collapse bulk operations
- Set-based deduplication of file paths
- Ignores: `node_modules`, `.git`, `.vite`, `.next`, `.cache`, `dist`, etc.
- Emits `"changes"` event with deduplicated paths

## Terminal

`src/server/session/terminal.ts` — wraps `node-pty` for interactive shell.

- Spawns shell in the workspace directory
- Emits `"data"` for output, `"exit"` on close
- Supports resize during session
- Output buffer capped at 10K characters

## Resource Limits

| Resource | Limit | Location |
|----------|-------|----------|
| Message queue | 50 | `SessionRunnerInterface` |
| Turn event buffer | 1,000 | `SessionRunnerInterface` |
| Terminal output buffer | 10K chars | `SessionRunnerInterface` |
| Service log buffer | 80K chars | `ServiceManager` |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…