<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>sessionpipe releases</title>
<link href="https://sessionpipe.org/changelog/"/>
<link rel="self" href="https://sessionpipe.org/changelog/feed.xml"/>
<id>https://sessionpipe.org/changelog/</id>
<updated>2026-10-29T00:00:00Z</updated>
<entry>
<title>sessionpipe 0.11.0</title>
<link href="https://sessionpipe.org/changelog/#v0.11.0"/>
<id>https://sessionpipe.org/changelog/#v0.11.0</id>
<updated>2026-10-29T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Session names from claude.ai, off until the person turns them on (&lt;code&gt;sessionpipe titles on&lt;/code&gt;, config.json &lt;code&gt;remote_titles&lt;/code&gt;). claude.ai names a session the Claude app starts seconds after its first message and never writes that name to the machine, so until now such a session went out under its first prompt cut to 50 characters while the Claude app showed another name. With it on, the worker asks claude.ai for the name of each Claude Code session with a claude.ai link (&lt;code&gt;GET https://api.anthropic.com/v1/code/sessions/&amp;lt;session_…&amp;gt;&lt;/code&gt;, as Claude Code reads a session; the answer recorded in &lt;code&gt;conformance/remote/claude-code/&lt;/code&gt;), a minute apart until it has one, then every half hour, within 3 s, and sends it as &lt;code&gt;session.title&lt;/code&gt; with &lt;code&gt;title_source: &amp;quot;harness&amp;quot;&lt;/code&gt;; a &lt;code&gt;custom&lt;/code&gt; title still wins. &lt;code&gt;titles on&lt;/code&gt; and &lt;code&gt;titles refresh [--days N]&lt;/code&gt; also ask about the last 30 days&amp;#39; sessions and send each one already sent under another name again as &lt;code&gt;session.backfill&lt;/code&gt;, so finished sessions are renamed too; a backfill takes these names as well. It bears the sign-in Claude Code keeps for that config dir (the macOS keychain item, else &lt;code&gt;.credentials.json&lt;/code&gt;), sends it to &lt;code&gt;api.anthropic.com&lt;/code&gt; alone, refuses redirects, never refreshes an expiring token and never writes it anywhere. Adapters gain an optional &lt;code&gt;remoteFacts&lt;/code&gt; and &lt;code&gt;BackfillRow.transcript&lt;/code&gt;; &lt;code&gt;Config&lt;/code&gt; gains &lt;code&gt;remote_titles&lt;/code&gt;. spec/ADAPTERS.md, spec/PRIVACY.md §3 and the &lt;code&gt;session.backfill&lt;/code&gt; row of spec/PROTOCOL.md say so.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;limits.observed&lt;/code&gt; reaches tier-1 sinks: after a Codex turn when its rollout holds new numbers, and from Claude Code&amp;#39;s status line with &lt;code&gt;install --status-line&lt;/code&gt; (or &lt;code&gt;connect --status-line&lt;/code&gt;; &lt;code&gt;--no-status-line&lt;/code&gt; takes it away; remembered in the config). A status line hides most of Claude Code&amp;#39;s footer hints, so it is opt-in. The line shows the limits (&lt;code&gt;5h 23% · week 41%&lt;/code&gt;) and hands the worker a job only when they changed, at most once a minute per config dir and never without a tier-1 sink. Before this a receiver could not tell which account still had room in its 5-hour or weekly window.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;The pool: several Claude accounts behind one loopback listener the control daemon runs, so a Claude Code session keeps going when one account&amp;#39;s limit is reached. &lt;code&gt;sessionpipe pool add --import&lt;/code&gt; copies this machine&amp;#39;s Claude Code logins into the pool (&lt;code&gt;--login&lt;/code&gt; signs another account in: a URL, then the code pasted to &lt;code&gt;--code -&lt;/code&gt;), &lt;code&gt;pool on&lt;/code&gt; writes the &lt;code&gt;env&lt;/code&gt; block into every Claude Code &lt;code&gt;settings.json&lt;/code&gt; and installs the daemon&amp;#39;s service if it is missing, &lt;code&gt;pool&lt;/code&gt; lists the accounts in order with the room each has left, &lt;code&gt;order&lt;/code&gt;, &lt;code&gt;enable&lt;/code&gt;, &lt;code&gt;disable&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt; and &lt;code&gt;refresh&lt;/code&gt; shape the roster, and &lt;code&gt;pool off&lt;/code&gt; removes exactly what &lt;code&gt;on&lt;/code&gt; wrote. A request goes to the first account in your order that has room; a session stays on its account while that account has room; the numbers an account reports on a session&amp;#39;s request become a tier-1 &lt;code&gt;limits.observed&lt;/code&gt; with &lt;code&gt;account_id&lt;/code&gt; on that session, through the outbox like a hook&amp;#39;s. Every hello carries the pool (&lt;code&gt;pool&lt;/code&gt; object), a receiver&amp;#39;s signed &lt;code&gt;pool&lt;/code&gt; command drives it (&lt;code&gt;on&lt;/code&gt;, &lt;code&gt;off&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt;, &lt;code&gt;disable&lt;/code&gt; and &lt;code&gt;login.complete&lt;/code&gt; need a fresh confirm, never a day grant alone: &lt;code&gt;confirm_required&lt;/code&gt;), and while the daemon runs the CLI&amp;#39;s commands go to it over the local socket, since it holds the listener, the pins and the credentials. The daemon now runs for the pool alone, with no receiver paired, and &lt;code&gt;control off&lt;/code&gt; keeps its service while the pool is on; &lt;code&gt;sessionpipe uninstall&lt;/code&gt; runs &lt;code&gt;pool off&lt;/code&gt; first. Nothing of this happens until you run &lt;code&gt;pool add&lt;/code&gt; or &lt;code&gt;pool on&lt;/code&gt; yourself.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;The account pool&amp;#39;s contract (CONTROL.md §2 &lt;code&gt;pool&lt;/code&gt;, §6 &lt;code&gt;pool&lt;/code&gt;; PRIVACY.md §3): a hello MAY carry &lt;code&gt;pool&lt;/code&gt; (&lt;code&gt;on&lt;/code&gt;, &lt;code&gt;harness&lt;/code&gt;, &lt;code&gt;accounts&lt;/code&gt; in the machine&amp;#39;s order with &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;current&lt;/code&gt;, &lt;code&gt;until&lt;/code&gt;, &lt;code&gt;limited_by&lt;/code&gt; and the idle poll&amp;#39;s &lt;code&gt;windows&lt;/code&gt;; present in every hello of a daemon that knows the pool, &lt;code&gt;on: false&lt;/code&gt; and no accounts when nothing is set up), and a new signed kind &lt;code&gt;pool&lt;/code&gt; on the pseudo-session &lt;code&gt;sessionpipe:pool&lt;/code&gt; (&lt;code&gt;pool_action&lt;/code&gt;: on, off, enable, disable, remove, order, refresh, login.begin, login.complete; &lt;code&gt;pool_account&lt;/code&gt;, &lt;code&gt;pool_order&lt;/code&gt; of at most 20, &lt;code&gt;pool_code&lt;/code&gt; of one line ≤ 512 characters), verified like &lt;code&gt;key.add&lt;/code&gt;: each action takes exactly its own fields (&lt;code&gt;poolProblem&lt;/code&gt;, &lt;code&gt;POOL_SESSION&lt;/code&gt;, &lt;code&gt;POOL_ACTIONS&lt;/code&gt;). &lt;code&gt;limits.observed&lt;/code&gt; gains &lt;code&gt;account_id&lt;/code&gt;, the account the numbers belong to when a pooled session was served by another than its own; tier 1 carries it and &lt;code&gt;pii@1&lt;/code&gt; drops it as it drops &lt;code&gt;session.account_id&lt;/code&gt;. Schemas: &lt;code&gt;control-command&lt;/code&gt;, &lt;code&gt;control-hello&lt;/code&gt;, &lt;code&gt;event-data&lt;/code&gt;; control vectors 137–164.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Codex&amp;#39;s config.toml keeps its CRLF line ends through &lt;code&gt;install&lt;/code&gt; and &lt;code&gt;uninstall&lt;/code&gt;: the notify line goes in with the file&amp;#39;s own line end, and &lt;code&gt;uninstall&lt;/code&gt; leaves the file byte-identical, as it already did for an LF file.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;install&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;doctor&lt;/code&gt; read Gemini CLI&amp;#39;s version on Windows, where the CLI is npm&amp;#39;s &lt;code&gt;gemini.cmd&lt;/code&gt; shim; before, every Windows install said &amp;quot;could not run &lt;code&gt;gemini --version&lt;/code&gt;&amp;quot; and the 0.46 check never happened.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A hooks file that starts with a UTF-8 byte-order mark (PowerShell 5&amp;#39;s UTF8 encoding, older Windows editors) is read like any other and written back with the mark, so &lt;code&gt;install&lt;/code&gt; hooks it instead of reporting &amp;quot;not plain JSON; not touched&amp;quot;, and &lt;code&gt;uninstall&lt;/code&gt; leaves it byte-identical. The harnesses read such a file themselves.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sink add&lt;/code&gt; and &lt;code&gt;sink remove&lt;/code&gt; rewrite the hooks when a sink crosses tier 0, as &lt;code&gt;install&lt;/code&gt;&amp;#39;s own message says they will; before, the hooks stayed the lean set, &lt;code&gt;status&lt;/code&gt; read &lt;code&gt;stale&lt;/code&gt;, and &lt;code&gt;doctor&lt;/code&gt; blamed an old node or script path. &lt;code&gt;doctor&lt;/code&gt; now says which set the hooks are and why that no longer matches, when that is the cause.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sessionpipe tail&lt;/code&gt; names the outbox with the platform&amp;#39;s own separator: &lt;code&gt;~\AppData\Local\sessionpipe\outbox&lt;/code&gt; on Windows, where it read &lt;code&gt;…sessionpipe/outbox&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.10.0</title>
<link href="https://sessionpipe.org/changelog/#v0.10.0"/>
<id>https://sessionpipe.org/changelog/#v0.10.0</id>
<updated>2026-10-26T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Control can start, prompt and cancel any agent that speaks the Agent Client Protocol (CONTROL.md §6, &lt;code&gt;api&lt;/code&gt; mode; ADAPTERS.md &amp;quot;Agents driven through ACP&amp;quot;). The daemon runs the agent&amp;#39;s own CLI as a child on the launch line the ACP registry publishes (Gemini CLI, OpenCode, goose, Copilot CLI, Cursor, Droid, Devin, Kilo, Qwen Code, Cline and twenty more, 2026-10-22), is its JSON-RPC client, keeps the process for the next message, cancels with &lt;code&gt;session/cancel&lt;/code&gt;, reopens an idle session with &lt;code&gt;session/load&lt;/code&gt; where the agent can, and answers its permission requests from the machine&amp;#39;s mode (safe refuses and the reply says what; auto allows). Only an agent already installed is ever run (on PATH, or where &lt;code&gt;SESSIONPIPE_ACP_&amp;lt;HARNESS&amp;gt;&lt;/code&gt; points); the hello advertises the ones found. The pairing request and every hello list at most 20 harnesses, the schema&amp;#39;s limit (Claude Code, Codex and Antigravity first, then the agents in the registry&amp;#39;s order), and their &lt;code&gt;modes&lt;/code&gt; include &lt;code&gt;api&lt;/code&gt; whenever an ACP agent is listed.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;A Claude Code home that runs on another API now has an account. Such a home has no claude.ai login, and its &lt;code&gt;settings.json&lt;/code&gt; sets &lt;code&gt;env.ANTHROPIC_BASE_URL&lt;/code&gt; (Kimi K3 on a Modal endpoint, GLM on Z.ai). Its events carry &lt;code&gt;account_id: api-&amp;lt;16 hex&amp;gt;&lt;/code&gt;, made from the endpoint&amp;#39;s origin and the same on every machine that uses it. Control&amp;#39;s pair and hello name it by the endpoint&amp;#39;s host, in &lt;code&gt;accounts[].name&lt;/code&gt;, a new optional field. Before this, its sessions carried no account, so a board filed them with the harnesses that report none (a person&amp;#39;s Kimi sessions sat in &amp;quot;No account&amp;quot; beside 1,331 Codex ones). Only that one settings key is read, never the token or the headers beside it.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Control: device keys (CONTROL.md §2, §3.2). &lt;code&gt;sessionpipe-core/control&lt;/code&gt; verifies a device key&amp;#39;s signature (&lt;code&gt;{cred, sig}&lt;/code&gt;, raw r‖s over &lt;code&gt;sessionpipe.device.v1⏎&amp;lt;purpose&amp;gt;⏎&amp;lt;rp id&amp;gt;⏎&amp;lt;challenge&amp;gt;&lt;/code&gt;) for a grant, a confirm or a pairing proof, chosen by the machine&amp;#39;s own record of the key; &lt;code&gt;keyUsable&lt;/code&gt; closes key fields and checks a device key&amp;#39;s rules (&lt;code&gt;type: &amp;quot;device&amp;quot;&lt;/code&gt;, &lt;code&gt;alg -7&lt;/code&gt;, &lt;code&gt;store: &amp;quot;hardware&amp;quot;&lt;/code&gt;, &lt;code&gt;uv: &amp;quot;each-use&amp;quot;&lt;/code&gt;, id = SHA-256 of the SPKI); &lt;code&gt;verifyCommand&lt;/code&gt; refuses a &lt;code&gt;key.add&lt;/code&gt; whose key could never be used (&lt;code&gt;bad_fields&lt;/code&gt;). &lt;code&gt;verifyAssertion&lt;/code&gt; takes the purpose. New exports &lt;code&gt;DEVICE_CONTEXT&lt;/code&gt;, &lt;code&gt;deviceMessage&lt;/code&gt;, &lt;code&gt;deviceKeyId&lt;/code&gt;, &lt;code&gt;KEY_TYPES&lt;/code&gt;, &lt;code&gt;DEVICE_SIG_FIELDS&lt;/code&gt;, &lt;code&gt;DeviceSignature&lt;/code&gt;, &lt;code&gt;DeviceGrant&lt;/code&gt;. The daemon advertises &lt;code&gt;key_types: [&amp;quot;passkey&amp;quot;,&amp;quot;device&amp;quot;]&lt;/code&gt; when pairing and in every hello, stores a device key whole (from a pairing or a &lt;code&gt;key.add&lt;/code&gt;), and &lt;code&gt;control keys&lt;/code&gt; shows each key&amp;#39;s kind. 26 new shared vectors (&lt;code&gt;111&lt;/code&gt;–&lt;code&gt;136&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Control: a third machine mode, &lt;code&gt;inherit&lt;/code&gt;, so sessions you message run the way your own sessions on that machine run. &lt;code&gt;sessionpipe control mode inherit&lt;/code&gt; (or &lt;code&gt;--mode inherit&lt;/code&gt; on &lt;code&gt;control pair&lt;/code&gt; and &lt;code&gt;connect&lt;/code&gt;, or the whole word at pairing&amp;#39;s question) passes no permission flag, so each harness starts every turn in its own default there: Claude Code in its settings&amp;#39; &lt;code&gt;permissions.defaultMode&lt;/code&gt; (a resume too, and through the Agent SDK too), Codex as its &lt;code&gt;config.toml&lt;/code&gt;&amp;#39;s &lt;code&gt;approval_policy&lt;/code&gt; and &lt;code&gt;sandbox_mode&lt;/code&gt; say, Antigravity with no flag, as in safe. No mode passes a bypass flag. Before this, a machine whose Claude Code settings turn auto mode off (&lt;code&gt;permissions.disableAutoMode&lt;/code&gt;) had no mode that fit: &lt;code&gt;auto&lt;/code&gt; ran each messaged session in Claude Code&amp;#39;s default mode, which refused every command that needed approval, and &lt;code&gt;safe&lt;/code&gt; refuses them by design. Schemas: &lt;code&gt;control-pair&lt;/code&gt; and &lt;code&gt;control-hello&lt;/code&gt; &lt;code&gt;mode&lt;/code&gt; gains &lt;code&gt;inherit&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Control: a machine runs in &lt;code&gt;inherit&lt;/code&gt; mode only with receivers that know it. A receiver that predates &lt;code&gt;inherit&lt;/code&gt; shows such a machine as safe, while its sessions run with no permission flag. A receiver now says it shows the mode as what it is with &lt;code&gt;control.inherit: true&lt;/code&gt; in its well-known file (HTTP.md §1, CONTROL.md §2), and &lt;code&gt;sessionpipe control pair --mode inherit&lt;/code&gt; (and &lt;code&gt;connect --mode inherit&lt;/code&gt;) refuses, before anything is sent, a receiver that doesn&amp;#39;t: &amp;quot;… doesn&amp;#39;t know inherit mode yet; it would show this machine as safe while the sessions you message run with no permission flag&amp;quot;. &lt;code&gt;sessionpipe control mode inherit&lt;/code&gt; checks every paired receiver the same way and keeps the mode it had when one doesn&amp;#39;t know it, or can&amp;#39;t be read. Schema: &lt;code&gt;well-known&lt;/code&gt; &lt;code&gt;control.inherit&lt;/code&gt; (optional boolean). Conformance: &lt;code&gt;delivery/23-control-inherit&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;strong&gt;For receivers.&lt;/strong&gt; What a 0.10.0 machine sends and accepts, and how to adopt it while some machines still run 0.9.0 (CHANGELOG.md, &amp;quot;For receivers (0.10.0)&amp;quot;, has the detail).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Closed command field set.&lt;/strong&gt; Each command kind carries only its own fields; any other field inside the signed &lt;code&gt;cmd&lt;/code&gt; is refused &lt;code&gt;bad_fields&lt;/code&gt;, by the verifier and on the machine (0.9.0 ignored it). Keep your own data beside &lt;code&gt;cmd&lt;/code&gt;, never in it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mode: &amp;quot;inherit&amp;quot;&lt;/code&gt;&lt;/strong&gt; in pairing requests and hellos: no permission flag, so a start that names no level runs as the person&amp;#39;s own sessions there do, permission checks off included where their settings say so. Show it as &lt;code&gt;inherit&lt;/code&gt;, never as safe or auto. A 0.10.0 machine pairs in inherit, or switches to it, only with a receiver whose well-known file says &lt;code&gt;control.inherit: true&lt;/code&gt;; it tells everyone else&amp;#39;s person &amp;quot;… doesn&amp;#39;t know inherit mode yet; it would show this machine as safe&amp;quot;. Set the flag once you show the mode truthfully and accept it in &lt;code&gt;/pair&lt;/code&gt; and &lt;code&gt;/hello&lt;/code&gt; (conformance &lt;code&gt;delivery/23&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refusal code &lt;code&gt;mode&lt;/code&gt; is now &lt;code&gt;permission&lt;/code&gt;&lt;/strong&gt;, plus &lt;code&gt;refused&lt;/code&gt; &lt;code&gt;model&lt;/code&gt; and &lt;code&gt;failed&lt;/code&gt; &lt;code&gt;model&lt;/code&gt;. Map both codes; a 0.9.0 machine still says &lt;code&gt;mode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hello:&lt;/strong&gt; &lt;code&gt;start&lt;/code&gt; (per harness: &lt;code&gt;permissions&lt;/code&gt; among &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;safe&lt;/code&gt;, &lt;code&gt;edits&lt;/code&gt;, &lt;code&gt;auto&lt;/code&gt;, capped by the mode; the default &lt;code&gt;permission&lt;/code&gt;, which may be &lt;code&gt;inherit&lt;/code&gt;; &lt;code&gt;models&lt;/code&gt;, &lt;code&gt;any_model&lt;/code&gt;), &lt;code&gt;prompt_files: true&lt;/code&gt;, and no &lt;code&gt;home&lt;/code&gt;: a start&amp;#39;s &lt;code&gt;cwd&lt;/code&gt; may be &lt;code&gt;~&lt;/code&gt; or &lt;code&gt;~/…&lt;/code&gt; instead. &lt;code&gt;harnesses&lt;/code&gt; lists at most 20, and &lt;code&gt;modes&lt;/code&gt; include &lt;code&gt;api&lt;/code&gt; when an ACP agent is listed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acks:&lt;/strong&gt; a start&amp;#39;s &lt;code&gt;taken&lt;/code&gt; and &lt;code&gt;delivered&lt;/code&gt; carry &lt;code&gt;applied&lt;/code&gt; (&lt;code&gt;permission&lt;/code&gt; may be &lt;code&gt;inherit&lt;/code&gt;). Show &lt;code&gt;applied&lt;/code&gt;, never what was asked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Events:&lt;/strong&gt; &lt;code&gt;session.attended&lt;/code&gt;, &lt;code&gt;turn.ended.background&lt;/code&gt;, &lt;code&gt;turn.started.submitted_by&lt;/code&gt; and &lt;code&gt;injected&lt;/code&gt;, &lt;code&gt;session.launched_by&lt;/code&gt;, and &lt;code&gt;account_id&lt;/code&gt; &lt;code&gt;api-&amp;lt;16 hex&amp;gt;&lt;/code&gt; for a Claude Code home on another API, named in &lt;code&gt;accounts[]&lt;/code&gt; by &lt;code&gt;name&lt;/code&gt; with no &lt;code&gt;email&lt;/code&gt;. All optional, tier 0.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACP agents&lt;/strong&gt; (gemini-cli, opencode, goose, cursor, copilot-cli, droid and more) appear in &lt;code&gt;harnesses&lt;/code&gt; and in ack &lt;code&gt;session&lt;/code&gt; refs, acked in mode &lt;code&gt;api&lt;/code&gt;; they take no start options.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device keys:&lt;/strong&gt; an app that can&amp;#39;t run WebAuthn signs with a hardware-held P-256 key, &lt;code&gt;{id, alg: -7, spki, type: &amp;quot;device&amp;quot;, store: &amp;quot;hardware&amp;quot;, uv: &amp;quot;each-use&amp;quot;}&lt;/code&gt;, its signature &lt;code&gt;{cred, sig}&lt;/code&gt; (plus &lt;code&gt;str&lt;/code&gt; in a grant) where an assertion would stand; a 0.10.0 machine says &lt;code&gt;key_types: [&amp;quot;passkey&amp;quot;,&amp;quot;device&amp;quot;]&lt;/code&gt; when pairing and in every hello. To accept them, store and carry the key whole (all six fields) in a pairing&amp;#39;s and a &lt;code&gt;key.add&lt;/code&gt;&amp;#39;s &lt;code&gt;key&lt;/code&gt;, pass a &lt;code&gt;{cred, sig}&lt;/code&gt; grant, confirm or pairing proof through untouched, and say &lt;code&gt;control.signing.key_types: [&amp;quot;passkey&amp;quot;,&amp;quot;device&amp;quot;]&lt;/code&gt; in your well-known file. They are added only as passkeys are: at the terminal&amp;#39;s pairing code, or by a &lt;code&gt;key.add&lt;/code&gt; a trusted key signed. A receiver that doesn&amp;#39;t know them keeps working, passkeys only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;While machines run 0.9.0:&lt;/strong&gt; send &lt;code&gt;permission&lt;/code&gt; or &lt;code&gt;model&lt;/code&gt; only where the last hello&amp;#39;s &lt;code&gt;start&lt;/code&gt; advertises them (0.9.0 ignores them silently); &lt;code&gt;files&lt;/code&gt; on a &lt;code&gt;prompt&lt;/code&gt; only with &lt;code&gt;prompt_files: true&lt;/code&gt; (0.9.0 refuses them); a &lt;code&gt;~&lt;/code&gt; folder only to a machine whose hello has &lt;code&gt;start&lt;/code&gt;; never &lt;code&gt;inherit&lt;/code&gt; as a command&amp;#39;s level; a device key only for a machine whose last hello lists &lt;code&gt;device&lt;/code&gt; in &lt;code&gt;key_types&lt;/code&gt; (0.9.0 would store it as a passkey and refuse its signatures); read an absent &lt;code&gt;attended&lt;/code&gt;, &lt;code&gt;background&lt;/code&gt;, &lt;code&gt;submitted_by&lt;/code&gt;, &lt;code&gt;injected&lt;/code&gt; or &lt;code&gt;launched_by&lt;/code&gt; as unknown, never as false, 0 or a person.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;A control &lt;code&gt;prompt&lt;/code&gt; can carry &lt;code&gt;files&lt;/code&gt;, as a &lt;code&gt;start&lt;/code&gt; can (CONTROL.md §3.1, §6 &lt;code&gt;prompt&lt;/code&gt;), to a machine whose hello says &lt;code&gt;prompt_files: true&lt;/code&gt;. The daemon fetches and checks them as for a start, writes them to &lt;code&gt;&amp;lt;cwd&amp;gt;/.sessionpipe/files/&amp;lt;session&amp;gt;/&amp;lt;nonce&amp;gt;/&lt;/code&gt; (the command&amp;#39;s signed nonce, never the receiver&amp;#39;s unsigned message id), and appends the same &amp;quot;Attached files&amp;quot; block before the message is handed over in any mode: a resume, a fork, a held SDK session, an ACP agent (&lt;code&gt;api&lt;/code&gt;), a waiter or a Stop. A file that fails is acked &lt;code&gt;failed&lt;/code&gt; with code &lt;code&gt;file&lt;/code&gt; and the message is not delivered. A receiver no longer needs to put a link to an image into the message&amp;#39;s text.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;session.attended&lt;/code&gt; says whether a person attends the session: an optional tier-0 boolean in the harness&amp;#39;s own word, absent when the harness does not say (unknown, never &lt;code&gt;false&lt;/code&gt;). Claude Code fills it from &lt;code&gt;CLAUDE_CODE_SESSION_ATTENDED&lt;/code&gt;, counting a control turn and a session with a link (its Remote Control bridge, the desktop app) as attended: every headless run says &lt;code&gt;0&lt;/code&gt;, a Remote Control session included, and a &lt;code&gt;claude -p&lt;/code&gt; started inside one inherits &lt;code&gt;CLAUDE_CODE_ENVIRONMENT_KIND=bridge&lt;/code&gt;, so the link is what tells them apart. The hook carries &lt;code&gt;CLAUDE_CODE_SESSION_ATTENDED&lt;/code&gt; and &lt;code&gt;SESSIONPIPE_CONTROL_JOB&lt;/code&gt; into the job, and the worker takes those and &lt;code&gt;CLAUDE_CODE_HOST_SESSION_ID&lt;/code&gt; from the job alone, so a worker another session&amp;#39;s hook spawned no longer lends a swept job its own. Codex, Gemini CLI, Antigravity and Devin leave it absent.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;session.launched_by&lt;/code&gt;: the session that started this one, as &lt;code&gt;&amp;lt;harness&amp;gt;:&amp;lt;id&amp;gt;&lt;/code&gt;, read from &lt;code&gt;SESSIONPIPE_LAUNCHED_BY&lt;/code&gt; in the hook&amp;#39;s environment (PROTOCOL.md §3). A script that runs &lt;code&gt;claude -p&lt;/code&gt; for a session sets it, and a receiver can group the run under its launcher without reading it as a subagent. A subagent keeps its &lt;code&gt;parent_id&lt;/code&gt; and gets no &lt;code&gt;launched_by&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;A control &lt;code&gt;start&lt;/code&gt; can say how much the new session may do and which model it runs (CONTROL.md §2, §3.1, §5 step 8, §6 Options; ADAPTERS.md &amp;quot;Start options&amp;quot;). &lt;code&gt;permission&lt;/code&gt; is one of four levels, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;safe&lt;/code&gt;, &lt;code&gt;edits&lt;/code&gt; and &lt;code&gt;auto&lt;/code&gt;, with nothing above &lt;code&gt;auto&lt;/code&gt;; &lt;code&gt;model&lt;/code&gt; is the harness&amp;#39;s own id, passed as one argument. The machine&amp;#39;s &lt;code&gt;mode&lt;/code&gt; is the ceiling, and a start above it, or at a level the harness has no flag for, is refused &lt;code&gt;permission&lt;/code&gt; before anything is taken or run (the daemon&amp;#39;s old &lt;code&gt;mode&lt;/code&gt; code). On a machine in &lt;code&gt;inherit&lt;/code&gt; mode a start that names no level runs with no permission flag, as the person&amp;#39;s own sessions there do, and one that names a level may name only &lt;code&gt;read&lt;/code&gt; or &lt;code&gt;safe&lt;/code&gt;, since sessionpipe can&amp;#39;t rank the person&amp;#39;s own default; &lt;code&gt;inherit&lt;/code&gt; is never a level a command names. The hello advertises what each harness takes here (&lt;code&gt;start&lt;/code&gt;, capped by the mode: Claude Code&amp;#39;s levels from its &lt;code&gt;--help&lt;/code&gt;, Codex&amp;#39;s models from its own cache; a harness&amp;#39;s default may be &lt;code&gt;inherit&lt;/code&gt;). A start&amp;#39;s &lt;code&gt;cwd&lt;/code&gt; may be &lt;code&gt;~&lt;/code&gt; or &lt;code&gt;~/…&lt;/code&gt;, read under the daemon user&amp;#39;s home, so a receiver can start a session in a folder an event reported without the machine ever telling it its home. A start&amp;#39;s level and model are recorded with the session, and every later resume or fork runs at the narrower of that and the machine&amp;#39;s mode then (at most &lt;code&gt;safe&lt;/code&gt; on an inherit machine for a session that named a level). The &lt;code&gt;taken&lt;/code&gt; and &lt;code&gt;delivered&lt;/code&gt; acks of a start carry &lt;code&gt;applied&lt;/code&gt; (its &lt;code&gt;permission&lt;/code&gt; may be &lt;code&gt;inherit&lt;/code&gt;); a model the harness rejects ends &lt;code&gt;failed&lt;/code&gt; with code &lt;code&gt;model&lt;/code&gt;. An ACP agent takes neither option: a start that names one is refused, never run without it. Each command kind now has a closed set of fields, so a field a machine doesn&amp;#39;t know is refused rather than ignored. &lt;code&gt;sessionpipe control status&lt;/code&gt; shows the options per harness. A start whose text begins with a dash no longer reaches &lt;code&gt;claude -p&lt;/code&gt; as a flag.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;turn.ended&lt;/code&gt; carries &lt;code&gt;background&lt;/code&gt;: how much background work the harness reports still in flight as the turn ends, each piece of which wakes the session when it finishes. Claude Code&amp;#39;s Stop hook lists it as &lt;code&gt;background_tasks&lt;/code&gt; (in every Stop recorded since 2.1.258): a background shell command, a monitor, a subagent, a workflow. A session that ended its turn waiting on a two-hour background command read as finished on its receiver, under &amp;quot;Last 24 hours&amp;quot;, until the command woke it. Tier 0, a count only: never what that work runs.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;turn.started&lt;/code&gt; can now say who started the turn: two optional tier-0 fields, &lt;code&gt;submitted_by&lt;/code&gt; (the harness&amp;#39;s own word for who submitted the prompt, forwarded verbatim and never mapped onto a vocabulary of the spec — a different vocabulary from &lt;code&gt;session.started.source&lt;/code&gt;) and &lt;code&gt;injected&lt;/code&gt; (&lt;code&gt;true&lt;/code&gt; when the prompt text is a block the harness wrote rather than words a person typed). Claude Code fills &lt;code&gt;submitted_by&lt;/code&gt; from &lt;code&gt;UserPromptSubmit.source&lt;/code&gt; when a payload carries that key — its hook schema documents &lt;code&gt;user&lt;/code&gt; · &lt;code&gt;sdk&lt;/code&gt; · &lt;code&gt;system&lt;/code&gt; · &lt;code&gt;loop_wakeup&lt;/code&gt; · &lt;code&gt;schedule_wakeup&lt;/code&gt; · &lt;code&gt;poll_event&lt;/code&gt; and says a payload may omit it while the field rolls out, and no build through 2.1.304 sends it — and sets &lt;code&gt;injected&lt;/code&gt; from a closed list of its own prompt wrappers (&lt;code&gt;&amp;lt;task-notification&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;cross-session-message …&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;system-reminder&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;local-command-stdout&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;command-…&amp;gt;&lt;/code&gt;), so a task notification, a peer message or a slash-command expansion is distinguishable from a person typing. &lt;code&gt;injected: true&lt;/code&gt; says only that the prompt is not the person&amp;#39;s words, never that no person caused the turn: a Talk or &lt;code&gt;sessionpipe wait&lt;/code&gt; delivery arrives inside a &lt;code&gt;&amp;lt;task-notification&amp;gt;&lt;/code&gt;. Both fields are absent when the harness does not say, and an absent field is unknown, never &amp;quot;the person&amp;quot;. &lt;code&gt;sessionpipe tail&lt;/code&gt; prints both. Codex, Gemini CLI, Antigravity and Devin leave both absent.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Two Node-only subpaths in &lt;code&gt;sessionpipe-core&lt;/code&gt;, so a receiver of the person&amp;#39;s own (a board on their machine) runs as a service with the daemon&amp;#39;s code rather than a copy of it. &lt;code&gt;sessionpipe-core/service&lt;/code&gt;: the launchd agent and the systemd user unit for any process (&lt;code&gt;serviceFiles&lt;/code&gt;, &lt;code&gt;installService&lt;/code&gt; and &lt;code&gt;uninstallService&lt;/code&gt;, each with a dry run that answers the files and commands and touches nothing, and &lt;code&gt;ensureLinger&lt;/code&gt;). &lt;code&gt;sessionpipe-core/version&lt;/code&gt;: &lt;code&gt;compareVersions&lt;/code&gt;, &lt;code&gt;isNewer&lt;/code&gt;, &lt;code&gt;parseVersion&lt;/code&gt;, &lt;code&gt;UPDATE_EXIT&lt;/code&gt; and &lt;code&gt;supervise&lt;/code&gt;, the parent that restarts a process onto new code where no service manager will. The root export is unchanged and still runs in a Worker and a browser. The control daemon now uses both; its service is the same, except that the unit&amp;#39;s &lt;code&gt;ExecStart&lt;/code&gt; quotes every argument.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.9.0</title>
<link href="https://sessionpipe.org/changelog/#v0.9.0"/>
<id>https://sessionpipe.org/changelog/#v0.9.0</id>
<updated>2026-10-23T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Control daemon: a &lt;code&gt;start&lt;/code&gt; may bring files, for every harness it can start (Claude Code, Codex, Antigravity). The daemon fetches each from &lt;code&gt;{control}/files/{sha256}&lt;/code&gt; with the machine&amp;#39;s token, stops reading past the signed size, checks length and hash, writes them under &lt;code&gt;&amp;lt;cwd&amp;gt;/.sessionpipe/files/&amp;lt;session&amp;gt;/&lt;/code&gt; (self-ignoring &lt;code&gt;.gitignore&lt;/code&gt;, no symlinks followed, exclusive create, mode 0600) and starts the session with the text plus the list of relative paths. Any file problem acks &lt;code&gt;failed&lt;/code&gt; with code &lt;code&gt;file&lt;/code&gt; and starts nothing. Pair and hello now say &lt;code&gt;files: true&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sessionpipe control folders [add|remove &amp;lt;dir&amp;gt;…]&lt;/code&gt;: change the folders that sessions you message may run in, at the machine, without pairing again. Before this, the folders were set only by &lt;code&gt;control pair --folder&lt;/code&gt;. On 2026-10-13 a Kimi session in &lt;code&gt;~/claude-kimi&lt;/code&gt; on a machine paired with &lt;code&gt;~/claude&lt;/code&gt; was refused (&amp;quot;that session&amp;#39;s folder isn&amp;#39;t one this machine allows&amp;quot;), and the only fix was a new pairing, with its passkey approval. A folder to add must exist and can&amp;#39;t be the home folder or above it (pair&amp;#39;s rule). The running daemon picks up the new list within seconds, and when the folders or the mode change it sends a hello at once, so the receiver&amp;#39;s folder picker doesn&amp;#39;t wait up to ten minutes for the next one.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;A &lt;code&gt;devin&lt;/code&gt; adapter: Devin CLI (Cognition) sessions are reported like any other harness. &lt;code&gt;sessionpipe install&lt;/code&gt; (or &lt;code&gt;--devin&lt;/code&gt;) writes the eight event names Devin accepts into the &lt;code&gt;hooks&lt;/code&gt; key of its user config file; the payload maps to the protocol&amp;#39;s events, the folder comes from &lt;code&gt;DEVIN_PROJECT_DIR&lt;/code&gt; (nothing in the payload names it), and tier 2, the model, the title and the times are read read-only from Devin&amp;#39;s own SQLite session store — through a new &lt;code&gt;readers/sqlite.ts&lt;/code&gt; that works on Node 20 too (in-process &lt;code&gt;node:sqlite&lt;/code&gt;, else a short child process, else the &lt;code&gt;sqlite3&lt;/code&gt; CLI, else no turns). Eight fixtures recorded from real runs on 3000.11.3. Also: the &lt;code&gt;claude-code&lt;/code&gt; adapter now drops a payload with no &lt;code&gt;transcript_path&lt;/code&gt; and a non-UUID session id — that is another harness running our entry in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;, not Claude Code — and the worker logs a dropped event instead of discarding it in silence when no adapter owns its harness name.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Review fixes to the Devin adapter and two gaps it exposed.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;devin&lt;/code&gt;: a turn is now timed by the message&amp;#39;s own &lt;code&gt;metadata.created_at&lt;/code&gt;, not by &lt;code&gt;message_nodes.created_at&lt;/code&gt; — that column is the time the row batch was last &lt;strong&gt;saved&lt;/strong&gt;, one value for the whole chain, rewritten on every save and not monotonic along it, so every turn of a session was reported at the same wrong instant. The chain read is now bounded by the cursor and caps from the newest end: past 4000 nodes the old query kept the 4000 &lt;strong&gt;oldest&lt;/strong&gt; and then delivered nothing ever again, and every Stop re-read the chain&amp;#39;s root (a 17 kB system prompt and four more system blocks). &lt;code&gt;installed()&lt;/code&gt; reports &lt;code&gt;inactive&lt;/code&gt;, not &lt;code&gt;current&lt;/code&gt;, when the config file holds any event name Devin does not know — Devin then loads no hooks from that file at all, ours included — and &lt;code&gt;install()&lt;/code&gt; says so too. A &lt;code&gt;tool_response&lt;/code&gt; that is not an object no longer loses its output. &lt;code&gt;facts()&lt;/code&gt; takes the hook&amp;#39;s environment (new optional argument on the &lt;code&gt;Adapter&lt;/code&gt; interface), so a job swept by a worker another harness&amp;#39;s hook spawned still reads the right config and session store.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sessionpipe uninstall&lt;/code&gt; takes the same &lt;code&gt;--&amp;lt;harness&amp;gt;&lt;/code&gt; selection &lt;code&gt;install&lt;/code&gt; has, removes the shared hook launcher only on a full uninstall, and refuses an all-harnesses uninstall when &lt;code&gt;SESSIONPIPE_CONFIG&lt;/code&gt;/&lt;code&gt;SESSIONPIPE_STATE&lt;/code&gt; point away from this machine while other harnesses would still be read from &lt;code&gt;$HOME&lt;/code&gt; (pass &lt;code&gt;--all&lt;/code&gt; to mean it). The hook forwards &lt;code&gt;XDG_CONFIG_HOME&lt;/code&gt;, &lt;code&gt;XDG_DATA_HOME&lt;/code&gt; and &lt;code&gt;APPDATA&lt;/code&gt; in the job file, beside the per-harness variables already there. The control wait — up to 125 s on a &lt;code&gt;PermissionRequest&lt;/code&gt; — now applies the same &amp;quot;this is not a Claude Code payload&amp;quot; test the worker does, so our entry in &lt;code&gt;~/.claude/settings.json&lt;/code&gt; run by another harness returns at once. A payload no adapter claims is logged rather than dropped in silence.&lt;/p&gt;
&lt;p&gt;Tests: the Devin session-store tests build their fixture through the &lt;code&gt;sqlite3&lt;/code&gt; binary where &lt;code&gt;node:sqlite&lt;/code&gt; is absent, so they run on the Node 20 floor — where the &lt;code&gt;cli&lt;/code&gt; engine is the only way into a database, and where all seven used to skip. Each engine is asserted where it works and skipped only for itself; &lt;code&gt;inlineParams&lt;/code&gt; and the adapter&amp;#39;s own chain query are covered.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;turn.progress&lt;/code&gt; (tier 2): a turn still running goes out as it runs, not only when it ends. The prompt is sent as the turn starts, then what the assistant has said so far, at most every 20 s on a tool call and only when it changed, under the number its &lt;code&gt;turn.transcript&lt;/code&gt; will carry. Over 20 000 characters the reply keeps its end. A 21-minute turn used to show nothing on its receiver for 21 minutes.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A batch a receiver refuses with 413 is halved and sent again until it fits, and one event too large on its own is dropped and logged (HTTP.md §2). Before, the sink stopped for the run and the next worker sent the same batch again, so one oversized batch stopped a sink for good.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;An Antigravity session in a git checkout now opens its own conversation from its link, not Antigravity&amp;#39;s home. Antigravity keeps a git checkout&amp;#39;s folder under &lt;code&gt;gitFolder&lt;/code&gt; in &lt;code&gt;~/.gemini/config/projects/&amp;lt;id&amp;gt;.json&lt;/code&gt; (&lt;code&gt;{ &amp;quot;gitFolder&amp;quot;: { &amp;quot;folderUri&amp;quot;: … } }&lt;/code&gt;) and only a plain folder&amp;#39;s at the top of the resource, so the session&amp;#39;s project was never found for a repo: the link had no &lt;code&gt;?p=c/&amp;lt;conversation&amp;gt;?section=&amp;lt;project&amp;gt;&lt;/code&gt;, or named an enclosing plain folder&amp;#39;s project instead (6 of 8 projects on one machine, 2026-10-20).&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A busy session&amp;#39;s events no longer wait 6 s and go back to the queue behind its own lock. A lock whose worker is gone (its 15 s deadline ended it without a release) is taken at once instead of after 5 min; a job polls for the lock every 50 ms instead of doubling its wait, so siblings can&amp;#39;t keep taking it in between; and the sinks drain at once, so the lock is held for the slowest round trip, not the sum of them (33 re-queues in one day&amp;#39;s log on a machine with three sinks, 2026-10-13).&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Claude Code: a message the person sends while a turn is running now reaches &lt;code&gt;turn.transcript&lt;/code&gt; and &lt;code&gt;turn.progress&lt;/code&gt;. Claude Code records it as a &lt;code&gt;queued_command&lt;/code&gt; attachment rather than a &lt;code&gt;user&lt;/code&gt; record, so the transcript reader dropped it from tier 2.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The sessionpipe command&amp;#39;s entry runs only when invoked as the script (through its npm bin link too), so every command can be tested in process. Output and exit codes are unchanged.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The usage table behind &lt;code&gt;--help&lt;/code&gt; is exported (&lt;code&gt;usageTable&lt;/code&gt;), so sessionpipe.org&amp;#39;s docs page prints the same usage lines the CLI does; &lt;code&gt;--help&lt;/code&gt; output is unchanged. The README&amp;#39;s closing links sit at its end again, after &amp;quot;Help and status JSON&amp;quot;.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Skip the parent&amp;#39;s replayed history in forked Codex rollouts when reading session
facts and transcripts. Wait for an event naming the fork&amp;#39;s own thread, preserve
its original transcript timestamps and cursors, and leave its title unset until
it contains a real user ask of its own.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Codex: only the person&amp;#39;s own threads become sessions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An ephemeral thread is no longer reported: its hooks carry &lt;code&gt;transcript_path: null&lt;/code&gt; and its notify finds no rollout. Codex Desktop runs its own background work this way (ambient suggestions and their safety review, a new chat&amp;#39;s title), and so does &lt;code&gt;codex exec --ephemeral&lt;/code&gt;. Each used to arrive as an untitled session with no turns, often with &lt;code&gt;/&lt;/code&gt; as its folder.&lt;/li&gt;
&lt;li&gt;A subagent&amp;#39;s thread is no longer a session of its own. Its parent already reports it (&lt;code&gt;SubagentStart&lt;/code&gt; / &lt;code&gt;SubagentStop&lt;/code&gt;, its tool calls), so its notify and its rollout (backfill) report nothing. A fork the person made is still a session.&lt;/li&gt;
&lt;li&gt;Inside a subagent, Codex sends the parent&amp;#39;s &lt;code&gt;session_id&lt;/code&gt; with the subagent&amp;#39;s own rollout. That file is no longer read as the parent&amp;#39;s transcript, so the parent&amp;#39;s running turn (&lt;code&gt;turn.progress&lt;/code&gt;) no longer shows a subagent&amp;#39;s words.&lt;/li&gt;
&lt;li&gt;Conformance: a harness fixture with an empty &lt;code&gt;expect&lt;/code&gt; is a payload the adapter must report nothing for.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The control daemon retries pending events for every non-paused sink once a minute, including sinks whose token is in config.json. A session whose final delivery was refused no longer needs another hook event to reach its sink after the retry wait. The existing per-sink wait and machine-wide delivery lock still apply.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sessionpipe control pair&lt;/code&gt; names a first pairing the way the hooks name the machine: &lt;code&gt;machine&lt;/code&gt; from config.json, else the hostname without its &lt;code&gt;.local&lt;/code&gt;, &lt;code&gt;.lan&lt;/code&gt; or &lt;code&gt;.home&lt;/code&gt; suffix. On macOS the daemon used to register as &lt;code&gt;&amp;lt;name&amp;gt;.local&lt;/code&gt; while events said &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt;, so a receiver showed the same machine twice and could not route approvals to it. &lt;code&gt;--name&lt;/code&gt; and an existing pairing&amp;#39;s name still win.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Retry control polls after a 401 with a capped backoff, resume immediately when a hello accepts the token, and honour Retry-After on 503 responses. Show polling state, time since the last successful poll, and the last control error in status and doctor.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The control daemon&amp;#39;s socket is now mode 0600, as spec/CONTROL.md §8 says. It used to take the umask (0755 on a default macOS), so only its 0700 parent kept other users out.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The hook and worker entries run only when invoked as the script, so both can be tested in process. What they do, print and exit with is unchanged; the hook still imports node builtins only and its timing is the same.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Ignore Claude Code&amp;#39;s lone SessionEnd hooks (for example, from &lt;code&gt;claude mcp list&lt;/code&gt;) when there is no outbox, stored facts or existing transcript. These hooks no longer create an outbox or reach a sink or the local daemon. Real sessions still send their end, including when their transcript was deleted but an outbox or facts remain.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;No hook event is lost to a worker&amp;#39;s 15 s deadline, and none waits for an unrelated hook. A worker used to delete its job file before running it, so one ended mid-run (a sink that hung, a lock held out) took the event with it. A job is now claimed by renaming its file and the claim is released only once its events are in the outbox; a sweep gives a claim whose worker is gone back to the queue. A job the session&amp;#39;s lock held out used to wait in the queue for the next hook&amp;#39;s sweep, however long that took; a fresh worker now starts for it.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A machine under a flood of hook events no longer floods its sinks. One worker drains the sinks at a time; the others leave their events in the outbox for it and it sends them in full batches of 50, so four busy sessions cost a request per batch, not per event. A sink that answered 429 is not tried again by any worker before its &lt;code&gt;retry-after&lt;/code&gt;, and one that could not be reached waits 2 s · 10 s · 60 s · 5 min · 30 min, then every 30 min, as HTTP.md §2 has said all along (a machine with four busy sessions was refused 828 times in two minutes and had 5 192 jobs queued, 2026-10-14). The session&amp;#39;s lock now covers only the append, not the round trips.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;control pair&lt;/code&gt;: a code nobody approved before it expired is renewed in place, with fresh digits, for up to an hour. A person approving three headless machines from a phone ran out of ten-minute codes (2026-10-08). The expired code is dead on the receiver, so nothing lives longer than it did; a keyed pairing keeps the machine row its first code made. The terminal now also says how long a code is good for.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;turn.progress&lt;/code&gt;: a message absorbed by a running turn, and text the person never typed, no longer open a turn of their own. Claude Code reports a message sent mid-turn (the person&amp;#39;s, another session&amp;#39;s, a task notification) as a prompt carrying the running turn&amp;#39;s id; its reading went out numbered as the next turn, which the transcript never delivers, so a receiver kept showing it, with an empty reply, instead of the running turn. Such a prompt now leaves the reading on the turn the transcript is in, and a task notification that starts a turn is not shown as its ask. &lt;code&gt;isInjected&lt;/code&gt; also recognises a wrapper tag with attributes (&lt;code&gt;&amp;lt;cross-session-message from=&amp;quot;…&amp;quot;&amp;gt;…&amp;lt;/cross-session-message&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sessionpipe connect --machine NAME --rename-machine&lt;/code&gt; (and &lt;code&gt;install&lt;/code&gt;) now renames a paired control daemon too, so the hooks and the daemon stay one machine on a receiver. Before, setup refused to touch a paired daemon&amp;#39;s name, and a machine whose pairing predates the fix in 0.9.0 that names pairings like the hooks had no way to merge its two cards. The daemon says a new name in a hello at once, as it does for folders and mode.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;After a 429, the HTTP sink waits as &lt;code&gt;retry-after&lt;/code&gt; says when the header is an HTTP date (RFC 9110), not only when it is a number of seconds. Before, a date fell back to the 2 s backoff ladder.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A repeat &lt;code&gt;install&lt;/code&gt; or &lt;code&gt;connect&lt;/code&gt; on a config without a machine name keeps the name the hooks have been using (the hostname without &lt;code&gt;.local&lt;/code&gt;), and renames a control daemon paired under another name to it, instead of adopting the daemon&amp;#39;s. Before, a Mac paired before 0.9.0 (daemon &lt;code&gt;&amp;lt;host&amp;gt;.local&lt;/code&gt;, events &lt;code&gt;&amp;lt;host&amp;gt;&lt;/code&gt;) had its events switch to &lt;code&gt;&amp;lt;host&amp;gt;.local&lt;/code&gt; on the first update, so a receiver showed its new sessions under a second machine. Receivers key a pairing by id, so the daemon&amp;#39;s rename costs nothing.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A session&amp;#39;s short id (PROTOCOL.md §3): the first 8 hex digits of &lt;code&gt;sha256(&amp;quot;sessionpipe.session:&amp;quot; + harness + &amp;quot;:&amp;quot; + id)&lt;/code&gt;, what a person reads in a log line, a notification or a search box. Derived, never sent. Not a prefix of the id: Codex ids are time-ordered, and four of ninety on one machine shared their first eight characters. &lt;code&gt;shortId()&lt;/code&gt; in core; the worker&amp;#39;s log and the daemon&amp;#39;s lines that name a session use it.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Refuse connect/install downgrades of newer global packages or persistent hook copies,
including newer prereleases, unless --allow-downgrade is explicit. Preserve the
configured machine name when --machine differs; require --rename-machine for an
unpaired rename and refuse names that would split sessions from a configured daemon.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Treat a hook stdin that is JSON but not an object (&lt;code&gt;null&lt;/code&gt;, a number, a string, an array) as no payload in every adapter. Such a stdin made &lt;code&gt;fromHook&lt;/code&gt; throw on the first field it read, and the worker job for that hook died with it; it is now handled exactly like an empty stdin, so the hook reports nothing and the session carries on.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Internal cleanup: fallbacks no input could reach are gone from the adapters, the privacy filter, the secrets ruleset, the outbox reader and the ULID. No behaviour changes; the public API is unchanged.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Write an &lt;code&gt;ssh://git@host/owner/repo.git&lt;/code&gt; origin as &lt;code&gt;host/owner/repo&lt;/code&gt; in &lt;code&gt;session.repo&lt;/code&gt;, the same as the &lt;code&gt;git@host:owner/repo.git&lt;/code&gt; form of that remote. Before, the two spellings of one repository reached a receiver as two different repos.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Internal: the home directory is read when a path is asked for, not when the package loads. A process whose HOME changes after import (a test with a sandbox home) gets the new one; no user-facing change.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Draining a large session file no longer re-reads the rest of it for every batch: a read takes 1 MB (more only for one event longer than that), not the remaining file up to 8 MB, and counts its offset per line rather than from the start. A 10 MB session drained in batches of 50 decoded 55 MB.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Recover a session&amp;#39;s seq counter when its file is empty or not a number (a crash between truncate and write leaves it empty). Before, the counter restarted at 0 and the receiver&amp;#39;s deduplication silently dropped every later event of that session; now the next seq is read from the outbox file&amp;#39;s last line, as it already was when the counter file was missing.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The stdout sink answers with a retry result when its writer fails (a closed pipe under &lt;code&gt;sessionpipe tail | head&lt;/code&gt;), as the file and HTTP sinks do, instead of throwing out of &lt;code&gt;send()&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Stop the transcript cursor before a last line the harness is still writing. Before, a prompt whose line had no newline yet when the worker read the file was counted as read and never delivered, so the reply that followed arrived as a turn with an empty prompt. A complete last line without a trailing newline is still read.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The CLI entry runs only when invoked as the script, so the package can be imported by tests. Output is unchanged.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.8.0</title>
<link href="https://sessionpipe.org/changelog/#v0.8.0"/>
<id>https://sessionpipe.org/changelog/#v0.8.0</id>
<updated>2026-10-09T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Control&amp;#39;s pairing request and every daemon hello name the Claude Code accounts signed in on the machine (&lt;code&gt;accounts&lt;/code&gt;: &lt;code&gt;{harness, id, email}&lt;/code&gt; per config dir, read again for each hello), so a receiver the person paired with can label &lt;code&gt;session.account_id&lt;/code&gt; with the account&amp;#39;s email instead of showing an id. Events are unchanged: &lt;code&gt;account_id&lt;/code&gt; stays an id and no event carries an email. &lt;code&gt;sessionpipe control pair&lt;/code&gt; and &lt;code&gt;connect&lt;/code&gt; say which accounts they named.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Handle &lt;code&gt;--help&lt;/code&gt; and &lt;code&gt;-h&lt;/code&gt; before dispatching every CLI command, including nested commands, so asking for usage cannot install hooks, queue work, or contact a receiver. Implement &lt;code&gt;status --json&lt;/code&gt; with doctor&amp;#39;s names for shared facts plus pending session counts and queued jobs, preserving the human status output.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Build on TypeScript 7 (&lt;code&gt;baseUrl&lt;/code&gt; dropped from every tsconfig; &lt;code&gt;paths&lt;/code&gt; already resolved relative to the file). Dev-only: published output is unchanged.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.7.0</title>
<link href="https://sessionpipe.org/changelog/#v0.7.0"/>
<id>https://sessionpipe.org/changelog/#v0.7.0</id>
<updated>2026-10-04T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Control: messages reach Codex and Antigravity sessions too. The daemon starts a new
session with &lt;code&gt;codex exec --json&lt;/code&gt; or &lt;code&gt;agy -p … --output-format stream-json&lt;/code&gt;, and resumes
one with &lt;code&gt;codex exec resume&lt;/code&gt; or &lt;code&gt;agy --conversation&lt;/code&gt;, the way it drives &lt;code&gt;claude -p&lt;/code&gt;:
acked taken, the answer streamed as progress, then delivered with the reply. Both
harnesses pick their own session ids, so the daemon records the one a start got
(&lt;code&gt;state/control/aliases.json&lt;/code&gt;) and keeps the start&amp;#39;s id everywhere it reports the
session: acks, later messages, and the hook events. A message sent right behind its
start, even in the same poll, waits for it and resumes what it named. Safe mode runs
Codex in its &lt;code&gt;workspace-write&lt;/code&gt; sandbox and agy with no permission flag; auto mode passes
&lt;code&gt;--approve-for-me&lt;/code&gt; / &lt;code&gt;--mode accept-edits&lt;/code&gt;; the bypass flags are never used. Hello and
pairing advertise the harnesses installed on the machine. Core gains the runners
(&lt;code&gt;codexControl&lt;/code&gt;, &lt;code&gt;antigravityControl&lt;/code&gt;), a shared headless spawner (&lt;code&gt;runHeadless&lt;/code&gt;) and
&lt;code&gt;drivableHarnesses&lt;/code&gt;; the verifier accepts a &lt;code&gt;start&lt;/code&gt; for any harness.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Codex: a session&amp;#39;s turns and its title skip the AGENTS.md instructions Codex writes into the session as a &amp;quot;user&amp;quot; message, so a turn shows what the person asked.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.6.1</title>
<link href="https://sessionpipe.org/changelog/#v0.6.1"/>
<id>https://sessionpipe.org/changelog/#v0.6.1</id>
<updated>2026-10-04T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Antigravity: stop hooking PreToolUse. Antigravity reads a PreToolUse answer without a
&lt;code&gt;decision&lt;/code&gt; as a deny, so the &lt;code&gt;{}&lt;/code&gt; sessionpipe printed blocked every tool call on every
machine it was installed on, with no reason shown. Tool activity now comes from
PostToolUse (a &amp;quot;still working&amp;quot; beat, since Antigravity names no tool there).
&lt;code&gt;sessionpipe install&lt;/code&gt; / &lt;code&gt;update&lt;/code&gt; (and the daemon&amp;#39;s auto-update) rewrite the old entry; a
conversation that started before the rewrite still has it loaded, and gets an &lt;code&gt;ask&lt;/code&gt; with
a reason instead of a silent deny until it is restarted.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.6.0</title>
<link href="https://sessionpipe.org/changelog/#v0.6.0"/>
<id>https://sessionpipe.org/changelog/#v0.6.0</id>
<updated>2026-10-04T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;The control daemon updates itself. About a minute after it starts and then once a day (the last check is kept in &lt;code&gt;state/control/update.json&lt;/code&gt;, so restarts and sleeping machines don&amp;#39;t ask more often), it reads npm&amp;#39;s &lt;code&gt;latest&lt;/code&gt;; when that is newer it waits until nothing is in hand (no delivery, headless run, start, SDK turn, open permission prompt, session mid-turn or ack flush), pauses intake, installs with &lt;code&gt;npm install --global --ignore-scripts&lt;/code&gt; into the prefix it runs from, re-arms the hooks of the harnesses already set up, and restarts onto the new code: exit 75 under launchd or systemd (every service file ever written restarts on a non-zero exit; new ones also carry &lt;code&gt;SESSIONPIPE_SERVICE&lt;/code&gt; and &lt;code&gt;RestartForceExitStatus=75&lt;/code&gt;), and otherwise the old process stays as the new daemon&amp;#39;s parent. Off with &lt;code&gt;sessionpipe update off&lt;/code&gt; (&lt;code&gt;update_check: false&lt;/code&gt;, now actually read), &lt;code&gt;SESSIONPIPE_NO_UPDATE_CHECK&lt;/code&gt;, or when the copy isn&amp;#39;t a writable global npm install. &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;doctor&lt;/code&gt; show whether it&amp;#39;s on, the last check and the latest version.&lt;/p&gt;
&lt;p&gt;One daemon per machine: &lt;code&gt;control run&lt;/code&gt; no longer takes the socket from a daemon that answers on it (it says so and exits 0), steps aside if another daemon replaces its socket file, and never deletes another daemon&amp;#39;s socket. &lt;code&gt;sessionpipe update&lt;/code&gt; installs through the same path (npm run by the daemon&amp;#39;s own node, the copy on disk checked, only the configured harnesses re-armed) and asks a running daemon to restart onto it once idle.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A started session&amp;#39;s first message puts the person&amp;#39;s words first and the &amp;quot;sent via&amp;quot; line after, so Claude Code names the session from what they asked instead of &amp;quot;Message via spacesheep.dev (sessionpipe control, s…&amp;quot;.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.5.0</title>
<link href="https://sessionpipe.org/changelog/#v0.5.0"/>
<id>https://sessionpipe.org/changelog/#v0.5.0</id>
<updated>2026-10-02T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sessionpipe connect &amp;lt;receiver&amp;gt;&lt;/code&gt;: one command and one approval set a machine up — hooks for every agent and every Claude Code account, a sink at tier 2 with the key the receiver hands over at pairing, signed messages, keys in the keychain, a daemon that outlives logout, 30 days backfilled. It asks nothing (safe mode unless &lt;code&gt;--mode auto&lt;/code&gt;; folders from &lt;code&gt;--folder&lt;/code&gt;, the folder it ran in, or where recent sessions ran — never the home folder) and a second run only fixes what&amp;#39;s missing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hooks run a launcher, &lt;code&gt;~/.local/share/sessionpipe/sessionpipe-hook&lt;/code&gt;, with its own copy of the hook: a Node upgrade, &lt;code&gt;nvm uninstall&lt;/code&gt; or a moved npm prefix no longer silently stops reporting. Re-installs move old entries to it in place.&lt;/li&gt;
&lt;li&gt;Keys (sink tokens and secrets, machine tokens) live in the macOS Keychain or a desktop session&amp;#39;s Secret Service when one is writable, else in the 0600 files as before; &lt;code&gt;sessionpipe secrets [move keychain|secret-service|file]&lt;/code&gt;. A token a locked keychain won&amp;#39;t hand over leaves events waiting (never unsigned); the daemon drains them from your session.&lt;/li&gt;
&lt;li&gt;The Linux daemon turns on systemd linger, so closing ssh no longer stops it; on a Mac with nobody at the screen it runs as a background launchd agent instead of failing.&lt;/li&gt;
&lt;li&gt;Before a headless run the daemon asks Claude Code whether that account is signed in (&lt;code&gt;claude auth status&lt;/code&gt;) and fails the message with what to do — on a Mac, that a keychain login can&amp;#39;t be read with nobody logged in at the screen — instead of Claude Code&amp;#39;s &amp;quot;please run /login&amp;quot;. &lt;code&gt;connect&lt;/code&gt; and &lt;code&gt;doctor&lt;/code&gt; list each account&amp;#39;s state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sink add&lt;/code&gt; keeps a sink&amp;#39;s existing key when only the tier changes (it used to drop it); pairing fills in a sink that has no key and takes &lt;code&gt;--tier&lt;/code&gt;; &lt;code&gt;control pair&lt;/code&gt; run from the home folder no longer allows all of it; &lt;code&gt;control.json&lt;/code&gt; keeps the machine&amp;#39;s own caps across a write.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Run control pairing from the global install when invoked through npx, and use a stable Homebrew Node path for the daemon service. Existing service files are repaired by pairing again with the fixed CLI; install and update alone do not rewrite them.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.4.2</title>
<link href="https://sessionpipe.org/changelog/#v0.4.2"/>
<id>https://sessionpipe.org/changelog/#v0.4.2</id>
<updated>2026-10-01T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;A message for a session the daemon&amp;#39;s own run is still working on (a start, a resume) waits for that run and resumes the session, instead of forking a copy that would work the same task in parallel.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.4.1</title>
<link href="https://sessionpipe.org/changelog/#v0.4.1"/>
<id>https://sessionpipe.org/changelog/#v0.4.1</id>
<updated>2026-10-01T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;control pair&lt;/code&gt; says the approval happens on any device where you&amp;#39;re signed in to the receiver, not necessarily the machine being paired (which may have no screen), and that several machines are paired one by one, each with its own digits.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.4.0</title>
<link href="https://sessionpipe.org/changelog/#v0.4.0"/>
<id>https://sessionpipe.org/changelog/#v0.4.0</id>
<updated>2026-10-01T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;key.add&lt;/code&gt;: a device the machine already trusts vouches for a new one (a phone with a different keychain), signed and verified like any command; the daemon adds the key to its store. Five new conformance vectors.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sessionpipe control pair&lt;/code&gt; asks once, at the machine, whether sessions you message run in auto or safe mode (no more silent safe default), and &lt;code&gt;sessionpipe control mode auto|safe&lt;/code&gt; changes it; the running daemon picks it up within seconds. Message text may be 20 000 characters (command 48 000): a 4 000 cap silently cut real task briefs.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;A receiver&amp;#39;s well-known &lt;code&gt;control.session_tools&lt;/code&gt; names MCP servers its sessions may use without asking (spacesheep names its own), so a safe-mode run answering a message can publish the page it was asked for. MCP server names only; built-in tools stay behind the person&amp;#39;s approval rules.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Answers stream: a non-final &lt;code&gt;progress&lt;/code&gt; ack carries the answer so far (whole, with a rising &lt;code&gt;seq&lt;/code&gt;). The daemon streams headless runs from Claude Code&amp;#39;s &lt;code&gt;stream-json&lt;/code&gt; output (token by token) and in-place deliveries from the session&amp;#39;s transcript (message by message); the final ack still carries the whole answer.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.3.0</title>
<link href="https://sessionpipe.org/changelog/#v0.3.0"/>
<id>https://sessionpipe.org/changelog/#v0.3.0</id>
<updated>2026-09-30T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Open pairing: &lt;code&gt;sessionpipe control pair spacesheep.dev&lt;/code&gt; needs no key where the receiver declares &lt;code&gt;control.open_pairing&lt;/code&gt;. One link, one passkey approval; the daemon proves itself on the status poll with a &lt;code&gt;poll_key&lt;/code&gt; only it holds, prints which account approved, and adds the sessions key the receiver hands over as its events sink (tier 0). A bare host means https, and a &lt;code&gt;--token&lt;/code&gt; with a character no key has (a pasted &lt;code&gt;ss_…&lt;/code&gt; placeholder) is refused in words.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.2.0</title>
<link href="https://sessionpipe.org/changelog/#v0.2.0"/>
<id>https://sessionpipe.org/changelog/#v0.2.0</id>
<updated>2026-09-30T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;The control daemon (M6): &lt;code&gt;sessionpipe control pair | status | keys | off | run&lt;/code&gt; and &lt;code&gt;sessionpipe wait&lt;/code&gt;. One daemon per computer verifies every command against keys enrolled at its terminal and delivers it in place when it can (a parked waiter, the session&amp;#39;s next Stop, an Agent SDK session it started), else as a headless resume or fork; Stop and PermissionRequest hooks answer only for verified messages, and only where control is paired. Claude Code&amp;#39;s PermissionRequest hook timeout is now 130 s (re-run &lt;code&gt;sessionpipe install&lt;/code&gt;). A machine caps new sessions itself (2 at once, 10 an hour, &lt;code&gt;limits&lt;/code&gt; in control.json) and runs at most 3 headless Claude Code processes at once; messages are handled concurrently, one session&amp;#39;s in order; after a receiver outage the daemon is polling again within seconds and logs &lt;code&gt;back after N s&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Control (M6): &lt;code&gt;sessionpipe-core/control&lt;/code&gt;, the one verifier for signed control commands (WebCrypto only, safe in Workers and browsers), with 60 shared conformance vectors; the control schemas rewritten for signed, per-machine delivery. The CLI ignores &lt;code&gt;sink add --control&lt;/code&gt; (control is per machine) and a retried batch reuses its &lt;code&gt;webhook-id&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.1.1</title>
<link href="https://sessionpipe.org/changelog/#v0.1.1"/>
<id>https://sessionpipe.org/changelog/#v0.1.1</id>
<updated>2026-09-28T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;The sink drain packs events from many sessions into one batch of up to 50 (a first install&amp;#39;s backfill is a handful of POSTs, not one per session); cursors move only after the batch was acknowledged.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;&lt;code&gt;sink test&lt;/code&gt; sends a heartbeat and then its &lt;code&gt;session.forgotten&lt;/code&gt;, so a receiver keeps no phantom session from the test.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Fixes from the first outside install (Yaroslav Bulatov, issues #8–#14 and advisory GHSA-8f6f-c3c9-j5p6): a config file that is not plain JSON is never written back; installs keep a file&amp;#39;s indent and final newline and uninstall deletes files sessionpipe created; jobs that lose the session-lock race are retried and swept, never lost; Claude Code &lt;code&gt;StopFailure&lt;/code&gt; → &lt;code&gt;turn.ended&lt;/code&gt; with &lt;code&gt;reason: error&lt;/code&gt;; &lt;code&gt;status&lt;/code&gt;/&lt;code&gt;doctor&lt;/code&gt; say when Gemini CLI won&amp;#39;t run hooks (&lt;code&gt;tools.enableHooks&lt;/code&gt;) and word the Codex review honestly; &lt;code&gt;doctor&lt;/code&gt; measures the wall clock a harness waits; with no sink above tier 0 only one tool event per harness is hooked; the npx bootstrap installs into &lt;code&gt;~/.local&lt;/code&gt; when npm&amp;#39;s prefix is Homebrew&amp;#39;s versioned Cellar; a bare &lt;code&gt;pass:&lt;/code&gt; is no longer a secret name; the outbox holds redacted strings at rest in 0600 files under 0700 directories.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;READMEs carry the published (unscoped) package names.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;patch&lt;/strong&gt; &lt;p&gt;Published under unscoped names (sessionpipe-core, sessionpipe-receiver, sessionpipe-conformance) until the @sessionpipe npm org exists.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
<entry>
<title>sessionpipe 0.1.0</title>
<link href="https://sessionpipe.org/changelog/#v0.1.0"/>
<id>https://sessionpipe.org/changelog/#v0.1.0</id>
<updated>2026-09-28T00:00:00Z</updated>
<content type="html">&lt;ul&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Core: adapters for Claude Code, Codex, Gemini CLI and Antigravity; the secrets@1 and pii@1 rulesets and the tier filter; outbox, cursors, locks; file, stdout and HTTP sinks. CLI: install, uninstall, sink, status, doctor, tail, backfill, forget, replay, update; the hook entry imports only node: builtins. Conformance: adapter fixture runner.&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;strong&gt;minor&lt;/strong&gt; &lt;p&gt;Protocol v1 draft: the Zod source of truth (&lt;code&gt;packages/core/schema/v1.ts&lt;/code&gt;) and the generated &lt;code&gt;schemas/v1/*.json&lt;/code&gt;; inferred runtime types.&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;</content>
</entry>
</feed>
