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

throughline

v0.5.0

Published

Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)

Downloads

751

Readme

Throughline

npm version license node CI

English · 日本語

Cut ~90% of Claude Code's context usage while keeping nearly all the memory. Throughline separates conversation by type, not time — humans-readable text stays, machine-generated tool output retires to SQLite. Same decisions, same context, 90% lighter.

In 30 seconds

npm install -g throughline
throughline install     # registers hooks/skills and provisions the VS Code monitor task

That's it. Open any Claude Code session and your turns flow into ~/.throughline/throughline.db automatically. After 50 turns of work, just type /clear — the new session resumes mid-thought instead of starting from zero. (For non-/clear boundaries such as a brand-new chat or a VSCode restart, type /tl first to mark the predecessor.)

Global install also registers Codex UserPromptSubmit, PostToolUse, and Stop hooks in ~/.codex/hooks.json and enables both [features].codex_hooks = true and [features].hooks = true in ~/.codex/config.toml. The Codex hooks invoke the installed bin/throughline.mjs through an absolute Node path, so Codex App Server PATH differences do not hide the command. They are registered synchronously (async: false), matching the Codex hook behavior verified in Caveat. Existing non-Throughline Codex hooks are preserved. The prompt and tool-loop hooks capture rollout memory and write monitor state, but they do not inject $throughline at usage thresholds; token-monitor is display-only and is never an auto-refresh trigger. It also installs a global $throughline Codex skill. Bare $throughline starts a new Codex thread through app-server, injects Throughline DB handoff memory as a developer item, and opens that thread in the selected host; ask explicitly for current-thread rollback diagnostics when you want the guarded trim --execute --host codex surface.

How it compares

| | Throughline | MemGPT / SummaryBufferMemory | Plain Claude Code | |---|---|---|---| | Compression axis | content type (text vs tool I/O) | recency (old → summarized) | none | | Coding-assistant fit | high — tool I/O is the heavy 80% | medium — also compresses what you want to keep | — | | /clear survival | ✅ via SQLite + typed /clear / /tl baton | depends on host | ❌ | | Auto-inheritance risk | low (typed /clear or /tl names the predecessor) | high | — | | Runtime deps | zero (Node 22.5+ built-in node:sqlite) | many | — | | Multi-session token monitor | ✅ Claude real message.usage; Codex rollout token_count when available | — | — |

In a typical Claude Code session, 80% of the context window is tool I/O — file reads, Bash output, grep results. This data is consumed the moment Claude acts on it, but it stays in the context forever, pushing you toward the window limit.

Throughline fixes this by separating conversation content by type, not time:

Without Throughline (50 turns, no /clear):
  Context = user text + assistant text + tool I/O + system messages
          ≈ 125,000 tokens (80% is tool I/O you'll never re-read)

With Throughline (50 turns → /clear → resume):
  Context = recent 20 turns of conversation text (L2)
          + older 30 turns as one-line summaries (L1)
          + zero tool I/O (L3 — retired to SQLite, on-demand)
          ≈ 13,000 tokens — same decisions, same context, 90% lighter

Unlike MemGPT or LangChain's SummaryBufferMemory which compress by recency (old = summarized), Throughline separates by content type: human-readable conversation stays, machine-generated tool output retires. This is purpose-built for coding assistants where tool I/O is heavy but transient.

The retired L3 data isn't lost — Claude can pull it back on demand via throughline detail <time> when a past turn's tool output becomes relevant again.

Throughline also ships a multi-session token monitor that reads real Anthropic API usage from the transcript JSONL (no length / 4 heuristics).


Three-layer memory model (schema v7)

flowchart LR
    T["Live turn<br/>user · assistant · tools · thinking"]
    T --> H["Stop hook"]
    H --> L2[("L2 · bodies<br/>verbatim text")]
    H --> L3[("L3 · details<br/>tool I/O · thinking")]
    H -. "async<br/>Haiku" .-> L1[("L1 · skeletons<br/>one-liners")]

    L2 -- "recent 20 turns" --> S["Next SessionStart<br/>injection"]
    L1 -- "older turns" --> S
    L3 -. "on demand · throughline detail" .-> S

    classDef l1 fill:#3aa0ff,stroke:#1a1f2e,color:#fff
    classDef l2 fill:#7c5cff,stroke:#1a1f2e,color:#fff
    classDef l3 fill:#4a5568,stroke:#1a1f2e,color:#fff
    class L1 l1
    class L2 l2
    class L3 l3

| Layer | Name | Where it lives | Content | Cost per turn | | ----- | ---------- | --------------------- | --------------------------------------------------------------------- | ------------- | | L1 | Skeleton | injected when old | one-line summary of the turn (current Claude-primary path: Claude Haiku, optional Codex sidecar) | ~10 tok | | L2 | Body | injected when recent | user text + assistant reply, verbatim | full natural | | L3 | Detail | SQLite only | tool I/O, system messages, images, extended thinking (on-demand) | heavy, retired |

The layers are complementary and disjoint — nothing is duplicated across them. Extended thinking blocks are stored at L3 (kind='thinking') so the next session can see what the previous Claude was thinking at the moment it was interrupted, not just what it said aloud. On SessionStart the thinking of the final turn is injected inline above the L2 history; older thinking remains retrievable via throughline detail <time>.

On SessionStart, Throughline rebuilds the context from SQLite and injects it as plain text:

  • The most recent 20 turns are injected as full L2 (bodies) text
  • Older turns are injected as L1 (skeletons) one-liners
  • L3 stays in SQLite and is retrieved on demand via /sc-detail <time>

L1 summaries are generated lazily: for sessions that stay under 20 turns, no external summarizer is invoked. In the current Claude-primary path, Throughline uses Claude Haiku 4.5 via a subprocess (claude -p --model claude-haiku-4-5-*), reusing your Claude Max login — no API key required. When codex-sidecar is explicitly configured for the summarize-l1 preset, Throughline can use that instead. For Codex-primary capture, the L1 backend is the Codex CLI; failures are explicit and do not fall back to Claude Haiku or raw L2.

All three layers (L1/L2/L3) have working write paths as of schema v5. /sc-detail HH:MM:SS returns user/assistant text (L2) plus a kind-grouped view of tool inputs, tool outputs, and hook output captured at L3 for that turn.


Inheritance: typed /clear and /tl write a baton, source-clear is the fallback

Throughline 0.4.1+ supports two inheritance paths. The baton path is the primary route; the source-clear auto path is the fallback for cases where the user's /clear does not reach the UserPromptSubmit hook (for example the VSCode extension's menu-driven /clear).

flowchart LR
    U["User types<br/>/clear or /tl"] -->|UserPromptSubmit| W["writeBaton<br/>(session_id + TTL 1h)"]
    W --> B[("handoff_batons<br/>SQLite")]
    M["VSCode menu<br/>clear"] -->|no UserPromptSubmit| X["no baton"]
    NS["Next SessionStart"] --> C{"baton<br/>present?"}
    B -.-> C
    X -.-> C
    C -->|yes| P1["baton path<br/>(primary)<br/>merge that exact predecessor"]
    C -->|no, source='clear'| P2["auto path<br/>(fallback)<br/>findLatestClaudePredecessor"]
    C -->|no, source!='clear'| P3["fresh session<br/>no merge"]
    P1 --> INJ["inject L1 + L2 + L3 refs"]
    P2 --> INJ

    classDef primary fill:#7c5cff,stroke:#1a1f2e,color:#fff
    classDef fallback fill:#3aa0ff,stroke:#1a1f2e,color:#fff
    classDef neutral fill:#4a5568,stroke:#1a1f2e,color:#fff
    class P1 primary
    class P2 fallback
    class P3,INJ neutral

baton path (primary): typed /clear or /tl → deterministic inheritance

When the user types /clear or /tl in the prompt, the UserPromptSubmit hook writes a handoff baton with that session's session_id into the handoff_batons table. The next SessionStart (within the 1-hour TTL) consumes the baton and merges that exact predecessor's memory into the new session, regardless of the source value.

This path is deterministic: it names the predecessor by id rather than guessing, so multi-window scenarios where "most recently updated session" does not equal "the session you just /clear-ed" still inherit correctly.

typed /clear: Session A → /clear → Session B (consumes A's baton, merges A)
typed /tl:    Session A → /tl    → (new chat / restart) → Session B (consumes baton, merges A)

auto path (fallback): source='clear' → heuristic inheritance

Since Claude Code 2.1.128, the SessionStart hook receives source='clear' reliably after /clear. When no baton is present (for example because the /clear was triggered by the VSCode extension's menu and never reached UserPromptSubmit), Throughline falls back to findLatestClaudePredecessor to pick the most recent unmerged session for the same project and merges it.

Set THROUGHLINE_DISABLE_AUTO_HANDOFF=1 in your environment to opt out of this fallback. The env var only affects the fallback; typed /clear and /tl still write a baton and inherit because the user explicitly signalled "continue this work".

What gets injected

Both paths inject the same curated memory:

  • A "現在地 (latest exchange)" anchor (added in v0.4.12) re-surfaces the most recent user directive and the most recent assistant turn directly under the header, each truncated to 600 characters
  • L1 summaries (older turns, one-line)
  • L2 verbatim (most recent 20 turns, full text)
  • L3 references (throughline detail <time> retrieval commands, attached inline to each L1/L2 row; bodies stay in SQLite)

The injection is reframed as "resuming an interrupted task" rather than "reading past logs". The L2 verbatim already contains the last assistant turn — what Claude was about to do next — so no separate memo or extended thinking section is injected. The current-state anchor exists because, on long L2 windows, attention can fixate on the first L2 entry (the oldest turn in the window) and misread an old plan discussion as the current task; pinning the latest exchange at the top of the injection prevents that drift.

Each merged row keeps its origin_session_id, so repeated handoffs accumulate memory through chains:

S1 (4 turns) --/clear--> S2 (auto-merges S1, adds 3 turns) --/clear--> S3 (auto-merges S2, adds 5 turns)
                         origin=S1×4                                   origin=S1×4, S2×3, S3×5

Codex sidecar and Codex trim

Throughline is still Claude Code first. Codex support is an adapter layer: it can project the same HandoffRecord into a throughline_handoff JSON block, and it can optionally use codex-sidecar for read-only review / risk-check work when that tool is installed and configured. It does not replace Claude hooks, slash commands, transcript parsing, or /tl baton handoff behavior.

Useful inspection commands:

throughline handoff-preview --session <id>
throughline codex-summarize --session codex:<thread-id> --json
throughline codex-resume --session codex:<thread-id>
throughline codex-resume --session codex:<thread-id> --format handoff
throughline codex-handoff-start --session codex:<thread-id>
throughline codex-handoff-start --session codex:<thread-id> --execute --open-host vscode
throughline codex-handoff-smoke --session codex:<thread-id>
throughline codex-handoff-model-smoke --session codex:<thread-id> --dry-run --json
THROUGHLINE_EXPERIMENTAL_CODEX_HANDOFF_MODEL_SMOKE=1 \
  throughline codex-handoff-model-smoke --session codex:<thread-id> --json
throughline codex-resume --session codex:<thread-id> --format item-json
printf '**Next move**: continue the Codex implementation\n' \
  | throughline codex-resume --session codex:<thread-id> --memo-stdin
printf '**Next move**: continue the Codex implementation\n' \
  | THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1 \
      throughline codex-visibility-smoke --session codex:<thread-id> --memo-stdin \
        --request-timeout-ms 150000 --json
THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1 \
  throughline codex-visibility-smoke --session codex:<thread-id> \
    --resume-after-inject --request-timeout-ms 180000 --json
throughline codex-threads --limit 5
throughline codex-sidecar-diagnostics --project . --preset review
throughline codex-sidecar-dry-run --project . --preset risk-check \
  --context-file docs/throughline-handoff-context.example.json

The only existing external model call in core Throughline is L2→L1 summarization. When codex-sidecar is configured for summarize-l1, Throughline can use it for that step; otherwise it keeps the existing Claude Haiku path. This is an explicit compatibility mode, not silent auto-detection.

Codex current-thread rollback / inject is explicit-only. The 2026-05-06 incident initially looked like a rolled-back user prompt could reappear after VS Code restart / reconnect, and later live experiments showed token usage can drop briefly and then return in the same thread. For that reason, Codex hooks do not perform automatic current-thread refresh. throughline trim --execute --host codex remains available as an explicit diagnostic current-thread path when injectable DB memory is available. Bare $throughline instead starts a new thread with the handoff prompt, matching the safer Claude-style handoff model.

throughline codex-host-primitive-audit can inspect the installed Codex app-server schema read-only. On the current tested Codex CLI, it finds thread/rollback, thread/inject_items, and new-thread primitives, but no current-thread primitive that either clears/rewrites retained rollback sources or isolates/projects them away from model-visible input. This is now diagnostic evidence only, not an execute blocker. The audit also emits a host-agnostic same-thread repair contract: a passing design needs a current-thread non-resurrection primitive, rollback non-resurrection guarantee, memory reinjection, post-repair read verification, and a restart / reconnect non-resurrection smoke. VS Code evidence can inform that contract, but it is not itself the repair primitive.

Dry-run and preflight also inspect planned rollback risk. If the user text that would be removed is already present in Codex compacted.replacement_history, Throughline reports Planned rollback restore safety: risk as diagnostic context, but it no longer refuses preflight or execute on that basis.

The intended memory contract remains: older turns come back as L1 summaries, the latest 20 turns come back as full L2 conversation bodies, and L3 remains detail references instead of inline tool payloads. Throughline never guesses the active Codex thread: use throughline codex-threads to inspect read-only rollout candidates, then pass the chosen id explicitly. If a wrapper or host exports THROUGHLINE_CODEX_THREAD_ID or CODEX_THREAD_ID, throughline trim --host codex treats that as a current-thread identity signal; the CLI flag still wins when both are present. Throughline does not fall back to "latest rollout" guessing. When the chosen Codex thread has a current-project rollout, throughline trim can use the rollout as the trim source even if the Throughline DB has no captured Codex turn bodies; rollback events in the rollout are applied before the rollback plan is built. When DB memory exists, the injected memory is built from the Throughline DB rather than from the rollout preview; guarded execute refuses instead of injecting a rollout preview when Throughline DB memory is absent. During Codex preflight, Throughline also compares the rollout active-turn count with app-server thread/read / thread/resume counts. The separate read-only codex-restore-smoke also checks paginated thread/turns/list counts across fresh app-server processes. These are live app-server guards only; they are not by themselves a durable restart-safe proof. The guarded execute path is enabled by explicit --execute; it may still report execute-sent-live-only or execute-unverified if durable rollout evidence is not observed. Throughline reports execute-durable-verified when the rollout records a new rollback marker and records the injected active-work memory. Developer memory injection is item-level on current Codex hosts and may not add a host-visible turn during the immediate post-inject read; Throughline therefore uses the thread/inject_items response shape to decide whether a turn-count increase should be expected. doctor --codex also reports this context-refresh readiness explicitly: rollback source, inject memory source, the L1/L2/L3 memory contract, current L1/L2/L3 counts, the heuristic reduction estimate when rollout text is available, and the host primitive audit status as diagnostic context. L3 is reported as references-only; L3 bodies and tool payloads are not injected.

Codex trim dry-run reports a context reduction estimate when rollout text is available: rollback-candidate estimated tokens, injected-memory estimated tokens, and net estimated reduction. This is a chars / 4 heuristic from the rollout text, not an exact host tokenizer measurement. If rollback candidate turns are 0, there is no current trim saving under the active keep-recent setting.

Claude-side rewind UI itself is not driven by Throughline. The auto-handoff flow is /clear → new SessionStart → automatic injection of curated memory. Throughline does not invoke /rewind or any Claude Code internal command.

Codex-primary setup has an installed Stop hook after global throughline install. In real sessions, verify capture rather than assuming it: doctor --codex compares the current Codex thread with the latest captured DB session, and makes any missing Throughline DB advance visible. A Codex VSCode session that was already open before hook shape changes may not be a clean natural Stop smoke; use a newly started session or codex exec smoke for that check. A newly started VSCode-origin Codex session has been verified with matching current Codex thread and latest DB session in doctor --codex. The following commands are the explicit diagnostic, resume, smoke, and guarded trim surfaces:

throughline doctor --trim --host claude
throughline doctor --trim --host codex
throughline doctor --codex
throughline codex-capture --codex-thread-id <id> --json
throughline codex-summarize --session codex:<id> --json
throughline codex-resume --session codex:<id> --format handoff
throughline codex-handoff-start --session codex:<id>
throughline codex-handoff-start --session codex:<id> --print-prompt
throughline codex-handoff-smoke --session codex:<id> --json
throughline codex-handoff-model-smoke --session codex:<id> --dry-run --json
# optional model smoke; uses codex exec --ephemeral --sandbox read-only:
# THROUGHLINE_EXPERIMENTAL_CODEX_HANDOFF_MODEL_SMOKE=1 throughline codex-handoff-model-smoke --session codex:<id> --json
printf '**Next move**: continue the current Codex task\n' \
  | throughline codex-resume --session codex:<id> --memo-stdin
printf '**Next move**: continue the current implementation\n' \
  | throughline trim --dry-run --host claude --memo-stdin
throughline codex-threads --json --limit 5
throughline trim --dry-run --host codex --codex-thread-id <id>
throughline trim --dry-run --host codex --codex-thread-id <id> --preview-max-chars 4000
throughline trim --preflight --host codex --codex-thread-id <id>
CODEX_THREAD_ID=<id> throughline trim --preflight --host codex
throughline trim --execute --host codex --all
# read-only app-server process restart smoke; not full VS Code restart-safe proof:
# THROUGHLINE_EXPERIMENTAL_CODEX_RESTORE_SMOKE=1 throughline codex-restore-smoke --codex-thread-id <id> --json
# read-only local restore source inventory; not full VS Code restart-safe proof:
# throughline codex-restore-source-audit --codex-thread-id <id> --json
# manual two-phase VS Code reload/reconnect smoke:
# THROUGHLINE_EXPERIMENTAL_CODEX_VSCODE_RESTORE_SMOKE=1 throughline codex-vscode-restore-smoke --prepare --codex-thread-id <id> --json
# after reloading/reconnecting VS Code and sending the printed prompt:
# throughline codex-vscode-restore-smoke --verify --codex-thread-id <id> --marker <marker> --prepared-at <iso> --after-vscode-restart --json
# explicit current-thread trim:
throughline trim --execute --host codex --all --codex-thread-id <id>

For Codex trim, the default memory session is the current Codex thread: codex:<CODEX_THREAD_ID> / codex:<THROUGHLINE_CODEX_THREAD_ID>. Throughline does not fall back to the latest project session for Codex injection, because that can mix Claude-side memory into a Codex rollback. Pass --session only when deliberately injecting a different captured session.

That current-work framing matters: the original /tl design learned that L1/L2 memory alone can read like past logs rather than "the work in progress". The memo is one strong signal, but the broader mechanism is explicit structure: recent L2 is labeled as an active work thread, older hypotheses may be superseded by later entries, and the continuation instruction appears at the top and bottom of the injected memory.

For Codex-primary sessions, throughline codex-capture --codex-thread-id <id> stores active rollout turns under codex:<thread_id>, and throughline codex-summarize --session codex:<thread_id> can write older captured L2 turns to L1 with the Codex CLI backend. throughline codex-resume --session codex:<thread_id> renders that memory as an active-work context. --format handoff renders a shorter prompt for starting a new Codex thread without mutating the old one; it caps recent L2 entries, long body text, and detail references while pointing back to the full codex-resume context. --format item-json returns a Codex developer-message item for hosts that accept structured item injection; it is a rendering surface only and does not mutate the Codex thread by itself. --memo-stdin prepends an explicit Codex-primary current-work memo to that rendered context; this is a Codex-side opt-in for "what was I about to do next" signal, independent from the Claude /tl baton. throughline codex-visibility-smoke is the experimental mutation check for that rendered memory: it injects the active-work developer message and starts a marker-check model turn through the Codex app-server, so it requires the THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1 opt-in. In local real-host smoke testing, the marker appeared in item/agentMessage/delta; use --resume-after-inject to verify injected memory still survives a second thread/resume before turn/start, and use --request-timeout-ms / --timeout-ms when the model turn may take longer than the default wait. throughline codex-restore-smoke is the read-only restart diagnostic for the same boundary: it starts fresh Codex app-server processes and compares thread/read / thread/resume / paginated thread/turns/list turn counts with the rollout active turn count. Its proof scope is app_server_process_restart_only; even a passing result is not VS Code restart-safe proof for rollback / inject. With --inspect-risky-rollout, it can inspect a risky rollout read-only; if retained rollback text appears in app-server responses, the status becomes app-server-restore-text-retained when it appears in blocking candidates such as direct turn text or replacement_history, or app-server-restore-text-quoted when it appears only inside quoted/tool-output fields such as aggregatedOutput. The text-match report includes sample JSON paths, location kinds, risk classes, and blocking-candidates so quoted old output is not confused with resurrected user message fields. throughline codex-restore-source-audit is the local inventory companion: it checks the Codex rollout, session_index.jsonl, state_*.sqlite, and VS Code globalStorage / workspaceStorage candidates, VS Code settings.json, and VS Code logs for the chosen thread id and retained rollback text. SQLite-backed VS Code storage candidates such as .vscdb, .sqlite, .sqlite3, and .db are opened read-only and summarized by table / column / needle matches. It also scans installed OpenAI/Codex VS Code extension bundles for restore-path signals such as thread/read, thread/resume, thread/turns/list, reconnect needs_resume, persisted webview atoms, follow-up queue signals, and explicit rollback non-resurrection projection candidates such as replacement_history filter / tombstone paths. A match here is static evidence only; current tested storage roots can still report VS Code storage matches: 0 for the selected thread and retained rollback text. VS Code log matches are also classified into thread-id hits, retained rollback text hits, patch-apply failures, thread stream broadcasts, and replacement_history signals so incidental log mentions can be separated from restore-path evidence. Its proof scope is local_restore_source_inventory_only; it can narrow the restore-source hypothesis, but it still does not prove VS Code restart safety. These VS Code scans are diagnostics only. The product repair path remains the host-agnostic same-thread contract reported by codex-host-primitive-audit, not a VS Code-only implementation path. throughline codex-vscode-restore-smoke is the manual full-boundary protocol: --prepare injects a hidden active-work developer-memory marker, then VS Code must be reloaded or reconnected and asked a prompt that does not contain the marker. --verify scans the rollout for a marker answer after the prepare timestamp and rejects the proof if the marker leaked through the user prompt. Only a marker-free smoke prompt followed by an assistant answer whose trimmed text exactly equals the marker, plus --after-vscode-restart, is treated as restartSafe: true; prepare remains an explicit experiment gated by THROUGHLINE_EXPERIMENTAL_CODEX_VSCODE_RESTORE_SMOKE=1. A real VS Code reload/reconnect run has passed this marker proof, showing hidden developer memory can remain model-visible after reconnect. That is not the same as proving rollback-targeted user turns cannot resurrect, so rollback-specific smokes remain the stronger diagnostic boundary. throughline codex-vscode-rollback-smoke --verify --after-vscode-restart is the read-only verifier for that next proof: it requires a rollback event, rolled-back user text, a later user turn, and restoreSafety: ok before it will report restartSafe: true. throughline codex-rollback-model-visible-smoke is a stricter controlled experiment for the current thread: --prepare creates a unique user marker and rolls that turn back, while --verify later starts a model turn that contains only the marker prefix, not the full marker. It reports reproduced only if the model returns the hidden full marker. This mutates the thread and is gated by THROUGHLINE_EXPERIMENTAL_CODEX_ROLLBACK_MODEL_VISIBLE_SMOKE=1. Use --marker-file <path> for live runs so the full marker is stored locally instead of being printed into the same conversation being tested. Verify output reports rolledBackMarkerModelVisible separately from restartSafe. In a controlled 2026-05-08 current-thread run, both the immediate fresh app-server verify and the post VS Code reload/reconnect verify returned not-reproduced with no full marker in the prompt or observed assistant output.

A 2026-05-07 incident-shaped live rollback run produced useful risk evidence: thread_rolled_back and injected active-work memory were recorded in the rollout, but rollback-targeted user text remained in compacted.replacement_history, and the verifier later observed rolled-back user text reappearing in rollout/app-server diagnostics (restoreSafety: risk). A later risky restore inspection found those retained matches only inside aggregatedOutput, so this is not yet proof that the text is sent as a fresh user message or model-visible input. Because the controlled reproduction smoke stayed clean across app-server restart and VS Code reload/reconnect, Throughline no longer blocks Codex trim solely on this diagnostic risk.

For Codex, fresh-thread handoff remains an explicit continuation path in trim plans and trim diagnostics, but it is not a replacement for current-thread trim. The guided entrypoint is throughline codex-handoff-start --session codex:<thread-id>; it shows the structural smoke, model-smoke dry-run boundary, handoff render command, optional live model smoke, and can include the prompt with --print-prompt. With --memo-stdin, it also propagates --memo-stdin into the replay commands and reminds you to pipe the same memo when using them separately. Add --execute to create a new Codex app-server thread, inject the handoff memory as a developer item, and open it with --open-host auto|vscode|cli|none. The individual commands remain available: validate the fresh-thread handoff with throughline codex-handoff-smoke --session codex:<thread-id>, optionally audit the model-smoke boundary with throughline codex-handoff-model-smoke --session codex:<thread-id> --dry-run --json, render it with throughline codex-resume --session codex:<thread-id> --format handoff, then start a new Codex thread with that context. This does not mutate the current thread. trim --execute --host codex is the current-thread mutation path and still requires explicit execution, injectable Throughline DB memory, and explicit Codex thread identity. Human-readable dry-run output truncates the inline memory preview for scanability; the full text remains in --json as memoryPreview.text, and for Codex the fresh-thread continuation can be guided with codex-handoff-start or rendered directly with the codex-resume command shown in the fresh-thread continuation path.


Multi-session token monitor

Run:

throughline monitor            # all active sessions in the current project
throughline monitor --all      # every project, every session
throughline monitor --session <id-prefix>

Example output:

[Throughline] 1 セッション
▶ Throughline       Claude 2ed5039c just now ██░░░░░░░░  205.1k /   1.0M  claude-opus-4-6
  Throughline       Codex  019e085c just now ██████░░░░  151.9k / 258.4k  gpt-5.5
  • Claude token counts are accurate. Read straight from the latest message.usage field in the session transcript JSONL, which is what Anthropic's API actually reported (input_tokens + cache_creation_input_tokens + cache_read_input_tokens). No length / 4 approximation.
  • Codex token counts use the rollout token_count event when present. The monitor discovers live Codex rollouts directly from ~/.codex/sessions/**/rollout-*.jsonl, so a current Codex session can appear even before the Codex Stop hook writes codex:<thread_id> monitor state. While the monitor is running it reads the live rollout every tick and prefers the latest verified token_count sample. During an open Codex turn, the monitor overlays transient output_tokens on top of input_tokens; when task_complete arrives it drops back to verified input_tokens only. If a Codex rollout has no token-count event, Throughline can show an explicit estimate with estimated: true and the monitor marks it with est; it is not presented as exact usage.
  • Codex auto-refresh is disabled. The Codex UserPromptSubmit and PostToolUse hooks capture rollout memory and write monitor state, but they do not inject $throughline instructions. The Codex Stop hook also captures DB memory, writes monitor state, and stays quiet instead of sending rollback + injection above a threshold.
  • 1M-context detection is automatic. It checks the [1m] suffix in the transcript, falls back to string matching on 1M context, and finally promotes to 1M if observed usage exceeds 200k.
  • Multi-session view. Each Claude Code or Codex session writes its own state file (~/.throughline/state/<session_id>.json), and active Codex rollouts are discovered directly from the Codex session directory. Codex session ids are stored as codex:<thread_id> in JSON state, while the display shows the raw first 8 thread-id characters (for example 019e085c) to avoid the ambiguous codex:01 prefix slice. The monitor scans every second and displays one row per live session, sorted by last activity. The most recent one is highlighted with .
  • Stale hiding. Sessions that haven't been touched in 15 minutes drop out of the default view; files older than 24 hours are deleted entirely. This is the only time threshold in the system and is used solely for display hygiene — no memory decisions are made from it.
  • Line-wrap safe. Each line is truncated to process.stdout.columns - 1 before drawing, preserving ANSI color codes. The redraw cursor math cannot desync on narrow terminals.
  • Resize resilient via OSC 18t. Windows ConPTY + VS Code task terminals freeze process.stdout.columns at the PTY's initial size and never propagate panel resizes into Node, so polling or resize events can't catch them. Throughline queries the terminal itself with the CSI 18 t escape (\x1b[18t) every tick, parses the \x1b[8;rows;cols t reply off stdin in raw mode, and uses the real current width for truncation. On terminals that don't answer the query, the renderer falls back to process.stdout.columns → env.COLUMNS → 80. When the width changes the viewport is cleared in full (\x1b[2J\x1b[3J\x1b[H) before the next frame so the previous, wrongly-sized frame can't stack beneath it.
  • Per-row "last updated" stamp. Each session row carries an 8-cell just now / 24m ago stamp right after the session id, placed before the bar so narrow terminals don't truncate it. It follows the newest state, transcript, or Codex rollout mtime, so active sessions stay visible and the stamp can move before the next Stop hook completes. When you need more detail, throughline doctor --session <id-prefix> compares the state file against the actual transcript JSONL and flags drift, idle time, and /clear-induced transcript path staleness.
  • Live usage first, state snapshot as fallback. When the Stop hook finishes a turn it persists the latest tokens / model / contextWindowSize back into the state file. The monitor now prefers live Claude transcript / Codex rollout reads and uses the snapshot only when the live file cannot provide usage, so the display no longer waits for Stop to update.
  • Host-aware state. Missing host means an older Claude state file. Codex states use host: "codex", keep transcriptPath: null, and store the Codex rollout path separately as rolloutPath so the Claude transcript parser is never pointed at a Codex rollout.
  • Non-blocking Claude Stop hook (v0.3.22+). The Claude Stop hook is registered with "async": true so throughline process-turn runs in the background and does not delay Claude's reply from reaching you. L1 Haiku summarization (claude -p subprocess + inference, seconds to tens of seconds) would otherwise stall the user-facing response of every turn; since L1 is only needed for the next session's SessionStart injection, there is no reason to block the current turn on it. Existing installs need throughline uninstall && throughline install to promote the flag (the dedup logic skips entries that match by command string).

VS Code auto-start (automatic)

After throughline install, the current VS Code / Cursor / VSCodium project gets .vscode/tasks.json provisioned immediately when VS Code environment variables are present. Any other VS Code project you work in also gets the file on the first session event. The file configures runOn: folderOpen so the monitor appears in a dedicated terminal panel the next time you open that folder.

How it works. ensureMonitorTaskFile is called from throughline install and from all three Claude hooks (SessionStart, UserPromptSubmit, Stop). Whichever one fires first in your environment creates the file; the rest are idempotent no-ops. Once per project it inspects .vscode/tasks.json:

  • No file yet → creates one with a single Throughline Monitor task, and emits a one-time <system-reminder> to stdout so Claude tells you a Developer: Reload Window is needed to activate the folderOpen task once (v0.3.19+).
  • Plain JSON with other tasks → appends the monitor task, preserves your existing entries, version, and indentation (same notice fires once).
  • JSONC (comments or trailing commas) → does not touch the file. Prints a one-time notice to stderr asking you to paste the snippet below.
  • Already contains a Throughline Monitor task → does nothing (idempotent; this is the common path on every subsequent turn; notice is silent).

For Codex-primary projects, hook stdout is not always surfaced in the chat. throughline doctor --codex therefore reports the VS Code monitor task status, its runOn value, and the same Reload Window note in a visible diagnostic.

The generated task uses type: 'shell' with the absolute path to Node and bin/throughline.mjs. VS Code wraps shell tasks in a PTY (xterm.js) so the monitor sees isTTY=true, real columns, and resize events. Windows .cmd shims and missing PATH entries cannot break it because the command is already an absolute Node binary path.

Opt out: set THROUGHLINE_NO_VSCODE=1 in the environment used by Claude Code. Delete .vscode/tasks.json (or just the monitor entry) if you want to stop auto-start for a project that already has one.

Manual snippet for JSONC tasks.json files. If Throughline refused to edit your tasks.json because it contains comments or trailing commas, add this entry to the tasks array yourself:

{
  "label": "Throughline Monitor",
  "type": "shell",
  "command": "throughline monitor",
  "isBackground": true,
  "presentation": {
    "reveal": "always",
    "panel": "dedicated",
    "group": "throughline",
    "close": false,
    "echo": false,
    "focus": false,
    "showReuseMessage": false,
    "clear": true
  },
  "runOptions": { "runOn": "folderOpen" },
  "problemMatcher": []
}

Commands

| Command | What it does | | ---------------------------------------------- | ------------------------------------------------------------ | | throughline install | Register Claude user hooks/slash commands, the global Codex UserPromptSubmit/PostToolUse/Stop hooks, the global $throughline Codex skill, and the current VS Code monitor task when applicable | | throughline install --project | Register Claude hooks/slash commands in this repo only | | throughline uninstall | Remove Throughline-managed Claude hooks/slash commands, only the Throughline-managed Codex hook, and the $throughline Codex skill | | throughline monitor [--all] [--session <id>] | Run the multi-session token monitor | | throughline monitor --diag | Dump TTY/columns/env diagnostics (for debugging monitor render bugs) | | throughline detail <time> | Retrieve L2 body text and L3 tool I/O for a turn (see below) | | throughline doctor | Check Node version, hook registration, DB writability, PATH | | throughline doctor --session <id-prefix> | Diagnose a specific session — detect state/transcript drift, idle vs. stuck | | throughline doctor --trim --host claude\|codex | Diagnose trim host boundaries, manual procedure, and Codex host primitive blockage | | throughline doctor --codex | Diagnose Codex primary entry state, captured DB sessions, context-refresh memory contract, new-thread handoff readiness, safe continuation status, and host primitive audit | | throughline handoff-preview --session <id> | Print a Codex-facing throughline_handoff JSON projection | | throughline codex-capture --codex-thread-id <id> | Capture active Codex rollout turns into a codex:<thread_id> DB session | | throughline codex-summarize --session codex:<id> | Summarize captured Codex L2 into L1 with the Codex CLI backend | | throughline codex-resume --session codex:<id> | Render Codex active-work context from a captured Codex session | | throughline codex-resume --session codex:<id> --format handoff | Render a concise fresh-thread handoff prompt without mutating the current thread | | throughline codex-handoff-start --session codex:<id> | Guided start plan for moving handoff memory into a new Codex thread; add --execute to create the thread through app-server, inject developer memory, and open it with --open-host auto\|vscode\|cli\|none; use --print-prompt to include the prompt and --memo-stdin to carry a current-work memo | | throughline codex-handoff-smoke --session codex:<id> | Read-only validation that the fresh-thread handoff prompt is pasteable before starting a new thread | | throughline codex-handoff-model-smoke --session codex:<id> | Experimental marker smoke for the handoff prompt. --dry-run checks readiness / command boundary without starting Codex exec; --memo-stdin carries a current-work memo; live codex exec --ephemeral --sandbox read-only requires explicit env opt-in | | throughline codex-visibility-smoke --session codex:<id> | Experimental Codex app-server marker smoke; injects memory and starts a model turn | | throughline codex-restore-smoke --codex-thread-id <id> | Experimental read-only app-server restart restore smoke; --inspect-risky-rollout classifies retained rollback text as blocking retained text or quoted/tool-output text; does not prove VS Code restart safety | | throughline codex-restore-source-audit --codex-thread-id <id> | Read-only local restore-source inventory, including VS Code projection-candidate facts; does not prove VS Code restart safety | | throughline codex-host-primitive-audit | Read-only Codex app-server schema audit for same-thread rollback non-resurrection primitives and the host-agnostic repair contract | | throughline codex-vscode-restore-smoke --prepare/--verify --codex-thread-id <id> | Manual VS Code reload/reconnect marker proof protocol | | throughline codex-vscode-rollback-smoke --verify --codex-thread-id <id> | Manual VS Code rollback non-resurrection verifier | | throughline codex-threads | List read-only Codex thread id candidates for the current project | | throughline codex-sidecar-diagnostics | Check codex-sidecar diagnostics status for this project | | throughline codex-sidecar-dry-run | Print a normalized read-only sidecar request without running the app server | | throughline trim --dry-run --host codex | Preview Codex same-thread context trim memory and host boundary; does not rollback automatically | | throughline trim --preflight --host codex | Read/resume the explicit Codex thread and preview any app-server-count rollback adjustment without rollback/inject | | throughline trim --execute --host codex | Explicit diagnostic Codex current-thread rollback + Throughline DB memory inject; bare $throughline does not run this automatically | | throughline status | Print DB statistics (sessions, skeletons, bodies, details) | | throughline --version | Print the installed version |

Slash commands (invoked by the user in Claude Code):

| Command | What it does | | ------------- | ----------------------------------------------------------------- | | /tl | Write a handoff baton (explicit inheritance signal across non-/clear boundaries — new chat / VSCode restart) | | /clear | Built-in Claude Code reset. Throughline's UserPromptSubmit hook also writes a baton so the next session inherits the cleared session's memory | | /sc-detail <time> | Retrieve L2 body text and L3 tool I/O for a past turn |

Since v0.4.1, both /clear and /tl typed in the prompt write a baton identifying the current session, so the next SessionStart deterministically inherits that exact predecessor. The source='clear' auto path remains as a fallback for /clear triggered outside UserPromptSubmit (for example via the VSCode extension menu); THROUGHLINE_DISABLE_AUTO_HANDOFF=1 only opts out of that fallback.

Hook subcommands (invoked by Claude Code, not by humans): session-start (SessionStart), process-turn (Stop), prompt-submit (UserPromptSubmit — detects /tl and /clear and writes a baton).

throughline detail — for AI, not humans

throughline detail is the escape hatch Claude itself uses to pull archived detail back into the context when an L1 summary isn't enough. The injection footer explicitly instructs Claude to run this via its Bash tool when a past turn's tool I/O becomes relevant.

throughline detail 14:23:05          # single timestamp
throughline detail 14:23-14:30       # timestamp range

Output groups records by kind: L2 conversation bodies, then L3 tool input/ output, then system messages (hook output), then images. Records are scoped to the current project's merge chain so Claude only sees turns from its own project history.


Requirements

  • Node.js >= 22.5 (for the built-in node:sqlite module — no native build required, no npm install of SQLite bindings)
  • Claude Code with hooks support (SessionStart, Stop)
  • Claude Max subscription (for Haiku-based L1 summarization via claude -p)
  • Works on Windows, macOS, Linux

Throughline has zero runtime dependencies. The published tarball is just plain .mjs files.


Data layout

~/.throughline/
├── throughline.db          SQLite database (WAL mode)
├── haiku-workdir/            Isolated cwd for Haiku subprocess (recursion guard)
└── state/
    └── <session_id>.json     Per-session activity state for the monitor

Schema v7:

  • sessions — one row per session_id, with project_path and merged_into
  • skeletons — L1 one-liners, keyed by (session_id, origin_session_id, turn, role)
  • bodies — L2 verbatim text (user + assistant), same key shape
  • details — L3 records with kind column (tool_input / tool_output / system / image / thinking) and source_id for idempotent re-processing
  • handoff_batons — one row per project_path, with session_id and created_at. Written by the UserPromptSubmit hook when the user types /tl or /clear. Consumed and deleted by the next SessionStart if within the 1-hour TTL. (v8 dropped the memo_text column when memo was retired in v0.4.0.)
  • injection_log — audit trail of injection events

All memory tables carry an origin_session_id so rebonded rows keep their lineage across a chain of /tl handoffs.


Design principle: no fallback code

Throughline deliberately refuses to swallow unexpected errors. Silent try { … } catch { /* ignore */ } blocks hide bugs; instead, hooks throw and exit with a non-zero status so Claude Code surfaces the failure in stderr.

Specifically:

  • JSON parse failures → throw, not continue
  • Missing required fields → throw new Error(...), not exit(0)
  • DB transactions → explicit BEGIN IMMEDIATE / ROLLBACK / re-throw
  • Hook entry points wrap main() with a single .catch that writes stderr and exits with code 1

The only tolerated silent paths are:

  • JSONL per-line parse tolerance (tail partial writes are part of the format spec)
  • State-file corruption recovery (files are idempotently regenerated next turn)

See docs/PUBLIC_RELEASE_PLAN.md §0 for the full rule.


Haiku recursion defense

The default L1 summarization path spawns claude -p --model claude-haiku-4-5-* as a subprocess. Without precautions this would recursively fire the same Stop hook on the subprocess and infinite-loop. Two defenses stack:

  1. Isolated cwd. The subprocess runs in ~/.throughline/haiku-workdir/, a directory that contains no .claude/settings.json, so project-local hooks are never picked up by the child.
  2. Env var guard. The parent sets THROUGHLINE_IN_HAIKU_SUBPROCESS=1 in the child env. The Stop hook (turn-processor.mjs) exits immediately on line 1 if it sees this variable.

See src/haiku-summarizer.mjs for the implementation.


Troubleshooting

Monitor says 待機中 — アクティブなセッションがありません No session has touched its state file in the last 15 minutes. Send a message in Claude Code and the monitor should pick it up within 1 second. If it still does not, run throughline doctor.

Monitor seems stuck on the same value Each session row ends with a (Nm ago) stamp. If it keeps growing, the session is idle — no assistant turn has finished. For a deeper look, run throughline doctor --session <id-prefix> to compare the state file against the actual transcript JSONL and flag drift, idle time, or /clear-induced transcript path staleness.

throughline install wrote to the wrong settings file By default, Throughline installs to ~/.claude/settings.json (user scope, applies to all projects). Use --project to scope it to the current directory's .claude/settings.json instead.

Hooks never fire Run throughline doctor — it checks Node version, hook registration, DB writability, and PATH resolution. If the binary is not on PATH, reinstall with npm install -g throughline.

The most common cause is the npm global bin/ directory is not on PATH. This happens when you set npm config set prefix ~/.npm-global (the sudoless recommended setup) but only added the export to ~/.profile, not ~/.bashrc. VSCode's integrated terminal launches an interactive non-login bash, which reads .bashrc only — so the export is silently skipped and throughline is not found. Fix:

# bash: add to ~/.bashrc
if [ -d "$HOME/.npm-global/bin" ] ; then
    PATH="$HOME/.npm-global/bin:$PATH"
fi

Then reload the shell (source ~/.bashrc) and verify with which throughline.

throughline install itself prints a warning to stderr if PATH resolution fails at install time, but if you missed it, run throughline doctor again.

Using Throughline across Windows ↔ WSL2 (or Linux ↔ macOS)

Two things to know:

  1. The DB lives under os.homedir(), so each environment has its own database. C:\Users\<you>\.throughline\throughline.db (Windows) and /home/<you>/.throughline/throughline.db (WSL2) are unrelated. Memory does not roam. To migrate:

    # From WSL2, copy Windows-side DB into WSL2 home
    cp /mnt/c/Users/<you>/.throughline/throughline.db ~/.throughline/throughline.db

    Or vice versa. Do this with no Claude Code sessions running so WAL/SHM are clean.

  2. WSL2 sees Windows npm shims via /mnt/c/...AppData/Roaming/npm, which may be earlier on PATH than your WSL2-native npm-global bin. Symptom: which throughline returns a /mnt/c/... path even though you ran npm install -g throughline inside WSL2. Fix: ensure ~/.npm-global/bin is before the Windows npm path in PATH (the .bashrc snippet above prepends, so it does this naturally).

.vscode/tasks.json should not be committed to git (recommended)

The Throughline Monitor task that gets auto-generated contains absolute paths specific to your machine (process.execPath and the install location of throughline.mjs). For shared repositories, add one of these to .gitignore:

.vscode/tasks.json     # only ignore tasks.json (settings.json etc. stay shared)
.vscode/                # ignore the whole .vscode directory

Throughline detects this and prints a one-time recommendation when it first creates / merges / repairs the file. (You don't have to follow the advice — v0.3.23+ auto-repairs stale absolute paths anyway, so committing is not catastrophic.)

Cross-environment .vscode/tasks.json errors after switching machines

If you commit .vscode/tasks.json to git and pull it on a different machine (Windows ↔ WSL2, Linux ↔ macOS, etc.), the command and args paths inside were absolutized to the original machine. Throughline auto-detects this: on the next hook fire it rewrites just the command / args fields to the current environment, preserving any label / presentation customization you made. You'll see a one-time Reload Window notice in Claude Code when the repair happens.

node:sqlite warning on startup Node.js prints ExperimentalWarning: SQLite is an experimental feature on stderr. This is cosmetic — the module is stable enough for production and is used unchanged here.

Database got corrupted / want a clean slate Delete ~/.throughline/throughline.db (and the -shm / -wal companion files) and ~/.throughline/state/*.json. A fresh database with schema v7 is created on the next hook fire.

New session didn't inherit memory from the previous one Since v0.4.1, both typed /clear and /tl write a baton, and the auto path falls back on source='clear' for menu-driven /clear. If inheritance still did not happen, the most likely cause is one of: (a) the previous session was never recorded (no Stop hook fired — check throughline status), (b) the 1-hour baton TTL expired before the new session opened, (c) the new session's project_path (cwd) differs from the previous one, so they live in different session chains, or (d) you set THROUGHLINE_DISABLE_AUTO_HANDOFF=1 and the /clear came from the VSCode menu which never reaches UserPromptSubmit. Memory is still in SQLite — you can retrieve specific turns with /sc-detail <time>.


Development

git clone https://github.com/kitepon-rgb/Throughline.git
cd Throughline
npm link                              # Put `throughline` on PATH (dev only)
throughline install --project         # Register hooks for this repo only
node --test src/turn-processor.test.mjs src/session-merger.test.mjs

Run the monitor directly without a global install:

node src/token-monitor.mjs

When any Throughline hook fires for the first time in a folder, it auto-generates .vscode/tasks.json with an absolute path to the monitor executable for the current machine. The file is gitignored (since v0.4.1) because the absolute path is per-machine. Reload the VS Code window after the first generation to pick up the auto-start task.


Design docs


License

MIT — see LICENSE.