Skip to content

Claude Code

Since 0.5.0, τ-mux integrates with Claude Code on three planes, plus a native Claude Code pane (0.7.0). Each piece degrades independently — any subset works, and none of it can break the terminal (the PTY never depends on the integration).

Fourteen Claude Code shell hooks (session start/end, prompt/stop, API failures, subagent start/stop, compaction, cwd changes, task created/completed, idle/permission notifications) run a small bridge that forwards one normalized JSON event to the app (ht claude event). A per-session registry tracks each Claude Code session’s phase — working / waiting for input / approval needed / compacting / error — and attributes it to the pane it runs in (HT_SURFACE).

You see it as:

  • the Claude label pill — session title while working, yellow Waiting for input, red Approval needed, muted Compacting…, red error text on API failures (rate limit, overload);
  • a completion notification on every turn end (prompt + duration + cost), an error notification on API failures;
  • the plan panel mirroring Claude Code’s native task list — see below.
~/.claude/settings.json
{ "statusLine": { "type": "command", "command": "ht claude statusline" } }

Claude Code pipes a JSON snapshot to its statusline command on every assistant message. ht claude statusline renders a τ-mux-styled status line back into Claude Code — model, effort, directory, git branch, permission mode, PR badge, a color-coded context bar, session cost, ±lines, and rate-limit warnings at ≥80% — and tees the data into the sidebar: the cc ticker becomes Opus · 42% ctx · $0.31.

Cost, context %, rate limits, and the session title are numbers Claude Code computes itself — they always match /cost and /context. (Earlier versions parsed transcripts against a hand-maintained pricing table; that machinery is gone.)

Decision plane — remote approvals (opt-in)

Section titled “Decision plane — remote approvals (opt-in)”

With the approvals feature installed, Claude Code permission prompts are routed to a τ-mux ask-user modal — and to Telegram when the bridge is configured — with three answers: Allow, Deny, Answer in terminal. The modal shows the exact tool + input (ground truth, never a summary).

Fail-safe by construction: if τ-mux isn’t running, the modal times out, you pick “Answer in terminal”, or anything at all goes wrong, the bridge prints nothing and Claude Code shows its own prompt exactly as before. The gate can only ever add an answer path. Kill switch without uninstalling: HT_CLAUDE_APPROVALS=0.

When Claude Code runs in a terminal pane and asks permission to run a command, τ-mux can press Enter for you (its prompt’s default answer is Yes):

  • Manually, always available — command palette → “Approve Claude Code permission prompt”, or ht claude approve (answers the longest-waiting session, or --surface).
  • Automatically — Settings → Auto-approve Claude Code prompts (off by default), or ht claude auto-approve on|off|status. Every approval is written to that pane’s sidebar log, so there is a record of what was accepted unattended.

This is deliberately narrow. It only fires when Claude Code is showing its own prompt in that pane’s terminal; it never answers the τ-mux approval modal (there is no terminal prompt to answer in that case) and never types into the Claude Code pane. It re-checks the prompt is still on screen after the configured delay, so it can’t fire a stray Enter into a pane where you already answered. And if more than eight prompts arrive in a minute it pauses itself and notifies you — a prompt storm is not something to rubber-stamp.

Questions addressed to you are never auto-answered

Section titled “Questions addressed to you are never auto-answered”

Claude Code raises the same permission-prompt hook for an AskUserQuestion or ExitPlanMode modal as it does for “may I run this command”, with the same generic message — on the hook stream alone the two are indistinguishable. Two hooks scoped to AskUserQuestion|ExitPlanMode tell τ-mux when a choice modal is open, and both auto-approve and the manual ht claude approve refuse to act while one is up: pressing Enter on a choice modal picks its default option, which is not what “approve” means.

When the modal closes, τ-mux retracts the approval announcement it raised — so an answered question stops showing a pending-approval pill, and a later ht claude approve can’t type Enter into a pane with no prompt on screen. A genuine tool prompt is left untouched.

A notification arriving while a modal is open is attributed to the modal. If that ever misfires the result is a missed auto-approval — you press Enter yourself — never a stray keystroke.

A turn that asks permission more than once has every prompt answered, not just the first — τ-mux counts prompt announcements rather than state transitions, because Claude Code ships no “prompt resolved” hook.

Auto-approve hands a coding agent unattended consent for the commands it asks to run. Turn it on when you are supervising the pane, not as a permanent default.

Claude Code’s native task list (TaskCreate / TaskCompleted) is mirrored into the plan panel automatically, per session — no model cooperation needed. Completed tasks show as done, the first open task as active; the mirror is cleared when the session ends, and it coexists with pi plans (each agent gets its own slot).

The mirror survives an app restart: session state (identity, cwd, title, task list, spend) is persisted to claude-sessions.json in the config dir and reloaded at launch. Live state is deliberately not restored — a restored session comes back idle, with no in-flight turn and no pending approval, and the next hook event corrects the rest. Without this, a restart left a still-running session with a permanently empty plan panel, because hooks only report transitions and nothing would re-announce the tasks it had already created.

Because the mirrored plan and the turn-end notification feed the existing auto-continue engine, plan-anchored continuation works for Claude Code sessions under the same safety gates. Note that the engine runs on every turn end, so a workspace with no published plan records a “no plan published” skip — consecutive identical skips collapse into a single row with a ×N count rather than filling the audit panel.

Terminal window
# one-time: put the bridge + skill in place (from the τ-mux repo)
./claude-integration/install.sh
# wire everything into ~/.claude/settings.json (managed, reversible)
ht claude install # lifecycle + tasks + statusline
ht claude install --features approvals # opt-in: remote approvals
ht claude install --dry-run # preview the diff
ht claude uninstall # remove every managed entry

The installer makes a timestamped backup, merges additively (your existing hooks are untouched), is idempotent, refuses to rewrite a settings file it cannot parse, and never clobbers a user-defined statusline (it reports it as kept). See ht claude.

Terminal window
ht claude doctor # binary + version, hooks wired/missing, approvals,
# statusline, skill, app reachability
ht claude sessions # sessions the app has observed (phase, title, cost)
HT_CLAUDE_DEBUG=1 # surface bridge errors on stderr

The skill (v2) teaches Claude Code the interactive surfaces — ht ask for decisions, splits for long-running processes, ht browser / ht screenshot for verification, confirm-command gating for destructive bash. Everything the hooks automate (pills, ticker, notifications, the plan mirror, approvals) is explicitly not the model’s job — the skill says so, which keeps it short and reliable.

When Claude Code’s experimental agent teams are enabled, τ-mux shows a passive team sidebar pill (“3 members · 2/6 tasks”) read from the on-disk team state. Read-only and schema-defensive — the upstream feature is experimental.

Bridge and skill live in claude-integration/ in the repo; app-side state lives in a session registry with presenter / mirror / watcher modules. The full design — including the trust model — is documented in doc/system-claude-integration.md.