throughline 0.4.12 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-sidecar.yml +5 -0
- package/CHANGELOG.md +106 -0
- package/README.ja.md +37 -21
- package/README.md +47 -26
- package/docs/00_overview.md +34 -0
- package/docs/{L1_L2_L3_REDESIGN.md → 01_l1_l2_l3_redesign.md} +3 -3
- package/docs/{THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md → 02_clear_auto_handoff_plan.md} +6 -6
- package/docs/{INHERITANCE_ON_CLEAR_ONLY.md → 03_inheritance_on_clear_only.md} +3 -3
- package/docs/{PUBLIC_RELEASE_PLAN.md → 04_public_release_plan.md} +3 -3
- package/docs/{THROUGHLINE_CODEX_FIRST_ROADMAP.md → 05_codex_first_roadmap.md} +9 -9
- package/docs/{THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md → 06_codex_trim_rollback_fix_plan.md} +6 -6
- package/docs/{THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md → 07_codex_trim_implementation_plan.md} +10 -10
- package/docs/{THROUGHLINE_CODEX_DUAL_SUPPORT.md → 08_codex_dual_support.md} +8 -8
- package/docs/{throughline-rollback-context-trim-insight.md → 09_rollback_context_trim_insight.md} +5 -5
- package/docs/10_transcript_injection_plan.md +446 -0
- package/docs/{THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md → 11_codex_monitor_implementation_plan.md} +1 -1
- package/docs/12_desktop_clear_handoff_plan.md +215 -0
- package/docs/adr/0001-claude-primary-codex-adapter.md +22 -0
- package/docs/archive/README.md +3 -3
- package/docs/archive/THROUGHLINE_NEXT_STEPS.md +3 -3
- package/package.json +2 -1
- package/rag/01-hooks/raw/hooks-reference-extract.md +250 -0
- package/rag/01-hooks/raw/session-end-reasons.md +21 -0
- package/rag/02-messages-api/raw/messages-api-extract.md +126 -0
- package/rag/03-settings/raw/sessions-extract.md +64 -0
- package/rag/04-skills/raw/initialUserMessage-investigation.md +101 -0
- package/rag/INDEX.md +164 -0
- package/src/baton.mjs +2 -2
- package/src/db.mjs +2 -2
- package/src/hook-entrypoints.test.mjs +390 -0
- package/src/package-files.test.mjs +1 -0
- package/src/prompt-submit.mjs +132 -5
- package/src/resume-context.mjs +23 -6
- package/src/resume-context.test.mjs +21 -5
- package/src/session-merger.mjs +1 -1
- package/src/session-start.mjs +155 -14
- package/src/spike-transcript-writer.mjs +196 -0
- package/src/spike-transcript-writer.test.mjs +298 -0
- package/src/state-file.mjs +1 -1
- package/src/token-monitor.mjs +1 -1
- package/src/transcript-reader.mjs +71 -0
- package/src/turn-backfill.mjs +131 -0
- package/src/turn-backfill.test.mjs +213 -0
- package/src/turn-processor.mjs +28 -40
- /package/docs/{throughline-codex-trim-rollback-incident-report.md → audit-2026-05/codex-trim-rollback-incident-report.md} +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# 12 — Desktop /clear 引き継ぎ + L2 捕捉完全性 改良プラン
|
|
2
|
+
|
|
3
|
+
<!-- 前提: Fable級統括/実装は codex_work・composer 委譲主体(2026-07 時点) -->
|
|
4
|
+
|
|
5
|
+
status: **承認済み・実装中**(2026-07-11 オーナー承認。本文書が正本=チェックボックスが TODO を兼ねる)
|
|
6
|
+
|
|
7
|
+
## 統括の型(orchestrate スキル準拠。配置は 02_models.md 決定表の引用必須 — 2026-07-11 オーナー裁定の書式)
|
|
8
|
+
|
|
9
|
+
配置宣言は正典 dotagents/docs/02_models.md を開いて該当行を写す。引用なしの配置は書かない。本プランの根拠行(引用・2026-07-11 時点):
|
|
10
|
+
|
|
11
|
+
- **実装物量**(02_models.md:40):「中位=`gpt-5.6-terra`×medium・codex_work」+「`grok-composer-2.5-fast`=並ぶ第一選択(仕様固定+検証コマンド必須の委譲契約を厳守)」
|
|
12
|
+
→ 波の割当: 既存ファイル編集を含む波(install/doctor/bin 配線、既存テスト更新)= codex_work(隔離 worktree)。独立新規ファイル生成の波(session-end.mjs 本体・spike ロガー・新規テストファイル)= composer も第一選択、統括が波ごとに選び理由を残す。
|
|
13
|
+
- **監査・発見**(02_models.md:36):「`sonnet`×low・Workflow で明示」「中位=`gpt-5.6-terra`×medium・codex_auditor/explore」「`grok-4.5`・grok_agent / `grok -p`(並列 finder に好適)」
|
|
14
|
+
→ B-2 の保存構造棚卸しは grok 並列 finder + codex_explore の多角スイープ。
|
|
15
|
+
- **反証・検証**(02_models.md:37):「主 継承×high・refuter」→ B-1 着手前 refuter・A 設計の追加反証に適用(model 省略=主継承が許されるのは検証・反証・裁定系のみ)。
|
|
16
|
+
- **裁定・契約クリティカル**(02_models.md:35):「主 直轄(F)」→ schema v9・バトン上書き規則・捕捉契約変更・settings.json 操作・最終レビュー・コミット。着手前 refuter 1 回(ガードレール常時ON)。
|
|
17
|
+
- **オーナー実機操作 = H**(Desktop/VSCode での /clear 操作・最小再現)。
|
|
18
|
+
|
|
19
|
+
委譲は orchestrate スキル references/delegation-contract.md の 8 点セット(file:line 仕様・罠リスト・検証コマンド・合格条件・報告フォーマット・前提再検証義務)。委譲物は統括が diff レビュー + ゲート再実行してから採用。各 Workstream は独立に revert 可能な単位で刻み、波ごとに pathspec 明示コミット。エージェントに branch 切替・commit をさせない。
|
|
20
|
+
|
|
21
|
+
## Context
|
|
22
|
+
|
|
23
|
+
発端は「Claude Code Desktop の `/clear` で自動引き継ぎが発火しない」引き継ぎ書(Caveat: `claude-code-desktop-clear-sessionstart-source-startup-throughline`)。調査の過程で、発火しても**注入の中身が欠ける**別問題(L2 捕捉欠落)をオーナーの実機テストが炙り出した。オーナー裁定により両方を本スコープとする(2026-07-11)。
|
|
24
|
+
|
|
25
|
+
- **A: Desktop /clear 引き継ぎ発火** — Desktop は `source:"clear"` を送らない(VSCode 2.1.207 は送る。クライアント実装差で確定、バージョン交絡は棄却済み)。SessionEnd(reason='clear') バトン方式で解決する。
|
|
26
|
+
- **B: L2 捕捉の完全性** — 完了済み論理ターンの L2 欠落率: Desktop 27%・VSCode 41%(実会話の欠落例を目視確認済み)。二系統に分解:
|
|
27
|
+
- **B-1(Throughline 側で直せる)**: transcript に本文はあるのに bodies に無い欠落。原因は turn-processor が各 Stop で「最後の 1 ペア」しか保存しない設計([src/turn-processor.mjs](../src/turn-processor.mjs) の `getLastTurnPair` 単発保存)=Stop 不発/空振りが永久穴になる。→ 全ターンスキャン・バックフィル型に再設計。
|
|
28
|
+
- **B-2(原因未特定・CC/Desktop 側の可能性)**: Desktop で assistant 本文が transcript にそもそも書かれない/大幅遅延する。バグか仕様か未確定。最小再現で条件特定 → upstream 報告はその後に判断(オーナー承認制)。
|
|
29
|
+
|
|
30
|
+
## 調査で確定した事実(2026-07-11 実測・反証済み)
|
|
31
|
+
|
|
32
|
+
### 1. `/clear` はどのクライアントでも UserPromptSubmit hook に届かない
|
|
33
|
+
|
|
34
|
+
commit 75d79d7(2026-05-08「/clear writes baton」)は実運用ゼロ発火。決定的証拠は同一セッション内 /tl 対照実験 ×2:
|
|
35
|
+
|
|
36
|
+
- 2026-06-28 Novel(VSCode 2.1.195): `/tl` 06:15:38 バトン書込 → 27 秒後 `/clear`。届いていれば trigger:"clear" で上書きされるはずが、後継が消費したバトンは `baton_age_ms:26777` = /tl 時点のまま。
|
|
37
|
+
- 2026-07-11 Caveat(Desktop 2.1.205): `/tl` 11:51:21 → `/clear` → 後継 65a01d22 の消費バトンは `baton_age_ms:35591` =同型。
|
|
38
|
+
- 2026-07-11 Throughline(VSCode **2.1.207**): `/clear` 3 連発(13:15:46 / 13:16:18 / 13:16:54)でも baton-write.log に trigger:"clear" ゼロ(/tl の 1 件のみ)。最新版でも変わらず。
|
|
39
|
+
|
|
40
|
+
docs 整合: ビルトインコマンドは UserPromptSubmit(prompt 送信時)にも UserPromptExpansion(skill / custom command / mcp_prompt 展開時)にも乗らない。prompt-submit.mjs の /clear 分岐は無害な保険として残置。
|
|
41
|
+
|
|
42
|
+
### 2. VSCode と Desktop の /clear 挙動差(クライアント実装差で確定)
|
|
43
|
+
|
|
44
|
+
| クライアント | 実測バージョン | /clear の SessionStart | 検証方法 |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| VSCode (`entrypoint: claude-vscode`) | 2.1.195 / 2.1.199 / **2.1.207** | `source:"clear"` → auto path 発火・merged:true | inheritance-decision.log の source:"clear" 11+3 件、残存 transcript 8 件の entrypoint 実測 |
|
|
47
|
+
| Desktop (`entrypoint: claude-desktop`) | 2.1.205 | `source:"startup"`。旧/新どちらの transcript にも /clear 痕跡なし。**後継セッションは /clear 時でなく初回プロンプト送信時に生成**(65a01d22: SessionStart 11:51:56.968 → 初 user prompt 11:51:57.035、/clear はその 16 秒以上前) | 2026-07-11 の Desktop 実測ペア + 当日全 Desktop セッション |
|
|
48
|
+
|
|
49
|
+
バージョン交絡(2.1.200+ リグレッション説)は VSCode 2.1.207 実測で棄却。クライアント判別は hook から env `CLAUDE_CODE_ENTRYPOINT`(`claude-desktop` / `claude-vscode`)で可能。
|
|
50
|
+
|
|
51
|
+
### 3. 案A(startup 時間窓フォールバック)不採用の根拠
|
|
52
|
+
|
|
53
|
+
- 幽霊セッション: 2026-07-11 だけで project_path=/Users/kite の startup が **182 件**(03:02〜12:56、最短間隔 0.001 秒、全て bodies=0)。haiku-workdir に 207 件=headless `claude -p` も SessionStart hook を発火する。
|
|
54
|
+
- 幽霊がチェーンに入ると MAX_CHAIN_DEPTH=10([src/session-merger.mjs](../src/session-merger.mjs):14)へ数時間で到達し resolveMergeTarget throw → ターン捕捉が恒久停止=ここで記憶が本当に失われる。
|
|
55
|
+
- source='startup' は「/clear の後継」と「並行して開いた別窓」を原理的に区別できず、稼働中セッションのレコードを relabel して記憶を split する。「bodies>0 の前任だけ選ぶ」は前任側フィルタなので無効(refuter 検証済み)。
|
|
56
|
+
|
|
57
|
+
### 4. SessionEnd hook 仕様(公式 docs live fetch 2026-07-11)
|
|
58
|
+
|
|
59
|
+
- SessionEnd は実在し、matcher が `reason` でフィルタ可能。reason enum: **`clear` / `resume` / `logout` / `prompt_input_exit` / `bypass_permissions_disabled` / `other`**。
|
|
60
|
+
- SessionStart source enum: `startup` / `resume` / `clear` / `compact`。
|
|
61
|
+
- **SessionEnd hook のデフォルト timeout は 1.5 秒**(/clear にも適用)→ per-hook timeout 明示が必須(サイレント kill によるバトン喪失対策)。
|
|
62
|
+
- Desktop が /clear 時に SessionEnd(reason='clear') を実際に発火するかは**未検証**(source を誤ラベルするクライアントなので要実測)→ A Phase 1 spike。
|
|
63
|
+
|
|
64
|
+
### 5. オーナーの 4 セッション連続 /clear テスト(2026-07-11 13:15-13:17, VSCode 2.1.207)
|
|
65
|
+
|
|
66
|
+
チェーン 4ef0d886 → c3e4e5d8 → ea60a1c9 → 9e74467d。マージは 3 回全部成功(auto ×2 + baton ×1)だが、**テスト全体で捕捉された L2 は 1 ターンのみ**(書込 13:16:45)。clear #1 (13:15:46)・#2 (13:16:18) の注入時点で DB は空 → 空の記憶を注入 → 体感「引き継げてない」で正解。3 回目の成功は /tl ではなく捕捉が追いついたため。state ファイルの 299 bytes(usage 無し=[src/turn-processor.mjs](../src/turn-processor.mjs) の `!assistantTurn` 早期離脱痕跡)vs 397 bytes(フル実行)が Stop 空振りの証拠。
|
|
67
|
+
|
|
68
|
+
### 6. L2 捕捉欠落の全域調査(論理ターン単位・完了ターンのみ・haiku-workdir 除外)
|
|
69
|
+
|
|
70
|
+
| クライアント | セッション | 完了論理ターン | 未捕捉 | 率 |
|
|
71
|
+
|---|---|---|---|---|
|
|
72
|
+
| claude-desktop (2.1.205) | 13 | 133 | 36 | **27.1%** |
|
|
73
|
+
| claude-vscode (2.1.199-206) | 6 | 155 | 63 | **40.6%** |
|
|
74
|
+
|
|
75
|
+
- 論理ターン=「user テキスト → 後続 assistant 断片群の最後の断片」。捕捉判定は bodies の (origin_session_id, turn_number=最終断片 index, role='assistant') 存在。
|
|
76
|
+
- VSCode 側の欠落は **transcript に本文が現存する**(調査自体が transcript から読めている)= Throughline 側の捕捉漏れ → B-1 で修正可能。
|
|
77
|
+
- 実在確認済みの欠落例: f1ad5b6f (WebAICoding) の実会話ターン #1/#3/#6 等。
|
|
78
|
+
- 注意: queued メッセージ(連続 user テキスト)が論理ターンを水増しする可能性は残る=率は上限値の目安。
|
|
79
|
+
|
|
80
|
+
### 7. Desktop transcript の assistant 本文欠落(B-2・unconfirmed)
|
|
81
|
+
|
|
82
|
+
本調査セッション自身(d7650b10, Desktop 2.1.205)の実測: assistant エントリ 93 件の内訳 thinking 48 / tool_use 36 / **text 9**。12:44〜13:15 の長いターンには本文断片が約 8 個あったが、transcript に着地したのは 1 個だけ、しかも発話から**約 16 分遅れ**(13:12:48)。残りは transcript・プロジェクトディレクトリ・~/.claude 全域・Desktop の local-agent-mode-sessions のどこにも grep ヒットせず=ローカル永久欠落。短いターン(13:17 以降)の本文は着地している。バグか仕様か(正本がサーバ/アプリ側にある可能性)は未確定。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Phase 0 — 同期・安全網・正本化(憲法1/2)
|
|
87
|
+
|
|
88
|
+
- [x] git 同期状態の確認(origin/main と 0/0・stash なし・untracked `.agents/` は端末ローカル残置がオーナー裁定済み=触らない)
|
|
89
|
+
- [x] ベースラインゲート green 確認: `npm test` 549 pass / 0 fail(2026-07-11)
|
|
90
|
+
- [x] 本プランを docs/12 として正本化(本文書)
|
|
91
|
+
- [x] rag/01-hooks に SessionEnd reason enum を還流([session-end-reasons.md](../rag/01-hooks/raw/session-end-reasons.md))、rag/INDEX.md に Finding 8 追記
|
|
92
|
+
- [x] 今日の調査を caveat に記録: public `claude-code-clear-userpromptsubmit-hook`(confirmed)/ private `claude-code-desktop-assistant-transcript-jsonl`(tentative・B-2 で更新)
|
|
93
|
+
- [x] 実稼働デプロイ(2026-07-12): `npm i -g /Users/kite/Developer/Throughline`(symlink 化=リポ変更が即時反映。リリース時は registry 版へ戻す)
|
|
94
|
+
|
|
95
|
+
## Workstream B-1 — 捕捉のバックフィル化(先行。A の E2E 品質の前提。挙動修正レーン=挙動差を明文化して個別承認)
|
|
96
|
+
|
|
97
|
+
- [x] **B-1 設計の着手前 refuter**: 判定「目的は正当・原設計のままでは採用不可」。修正 7 件を採用: ①群レベル dedup 必須(部分捕捉済み群への再挿入は同一発話の重複ペアを 110 件量産——実在確認: d7650b10 turn14/15。割り込みは tool_result 内に埋まり user 境界として不可視のため 1 群複数 Stop が日常)②前任 transcript path は project_path から決定的導出(state は Stop 不発前任で存在しない)③junk 代表除外(session limit 通知等)④INSERT を 1 トランザクション ⑤created_at は transcript timestamp(now は一括回収で同一 ms に潰れ L2 窓・現在地アンカーの順序が tie で不定化)⑥readTranscript に isSidechain 防御 ⑦resume 直後 transcript の実測 1 回を検証項目に追加。棄却された懸念: 注入肥大化(20 ターンキャップで構造上起きない)・SessionStart レイテンシ(58MB transcript でも 155ms)
|
|
98
|
+
- [x] **turn-processor 再設計**: [src/turn-backfill.mjs](../src/turn-backfill.mjs) 新設(全論理ターン群走査 + 群レベル dedup + junk 除外 + timestamp created_at + 単一トランザクション)、turn-processor は毎 Stop でこれを呼ぶ。回収実績は `~/.throughline/logs/backfill.log`。機能検証済み: 実 transcript × 隔離 DB で回収 12 群・冪等(2 回目 0 挿入)・部分捕捉群の重複ガード・junk 0 行・created_at 順 = 会話順 【F: 統括直轄】
|
|
99
|
+
- [x] queued メッセージの扱いを明文化: 群 = 「user テキスト → 後続 assistant 断片群」なので、応答前に積まれた先行 queued user は断片 0 の群となり捕捉されない(現行 getLastTurnPair と同等の非対応。将来課題)
|
|
100
|
+
- [x] session-start のマージ直後にも同じバックフィルを前任 transcript に対して実行(project path からの決定的導出を優先し、state file は Stop 不発前任のため補助)→ 「/clear 直前ターンの取りこぼし」を注入前に回収。失敗は stderr + `backfill.log` に明示し、注入は継続
|
|
101
|
+
- [x] 診断ログ: バックフィルで回収したターン数を stderr ではなく `~/.throughline/logs/backfill.log` に記録(Stop / session-start 共通)
|
|
102
|
+
- [x] テスト: 全ターンスキャンの単体(群レベル dedup、冪等性、junk、timestamp、sidechain、path munging)と hook subprocess(multi-turn / state 無し前任)の特性化を追加。`npm test`: 559 pass / 0 fail(2026-07-12)
|
|
103
|
+
- [ ] 検証: 欠落率調査スクリプト(付録)を再実行し、新規セッションで欠落 0% を確認
|
|
104
|
+
|
|
105
|
+
## Workstream A — Desktop /clear 引き継ぎ発火(SessionEnd バトン方式)
|
|
106
|
+
|
|
107
|
+
### A Phase 1 — spike 実測(Desktop の SessionEnd 白黒判定)
|
|
108
|
+
|
|
109
|
+
- [x] spike ロガー hook(`spike/session-end-logger.mjs`): stdin 全 payload + `CLAUDE_CODE_ENTRYPOINT` + 受信時刻を記録 【A → codex_work `gpt-5.6-terra`×medium で実装、統括が検証 3 本再実行済み。sidecar 側 PROTOCOL_ERROR(報告 envelope の schema 不一致)が出たが成果物は worktree から採用】
|
|
110
|
+
- [x] `~/.claude/settings.json` に SessionEnd を一時登録(絶対パス node + 絶対パス spike・timeout 10 明示。バックアップ: `~/.claude-settings-backup-20260711-235419.tar.gz`。撤去 = SessionEnd ブロック削除 + spike ファイル削除)【F: 統括直轄】
|
|
111
|
+
- [x] 実測プロトコル 【H: オーナー操作 2026-07-12 15:00-15:10 UTC】: ①Desktop /clear(③はアプリ終了の代わりにセッション削除で実施)②放置 ③削除 ④VSCode /clear。結果:
|
|
112
|
+
|
|
113
|
+
| 操作 | SessionEnd 発火 | reason | 備考 |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| Desktop `/clear`(d93b0d5f) | **即時**(返答 15:05:52 → 15:05:59) | **`other`** | payload は session_id/prompt_id/reason/cwd/transcript_path のみ・判別子なし |
|
|
116
|
+
| Desktop 放置(a8ece26f) | 発火せず | — | |
|
|
117
|
+
| Desktop セッション削除(675493fb) | 発火(12 秒後) | **`other`** | payload 構造は /clear と完全同一 |
|
|
118
|
+
| VSCode `/clear`(fa43271f) | 即時 | **`clear`** | 42ms 後に後継 SessionStart(source=clear)→auto merge。仕組み自体は健全 |
|
|
119
|
+
|
|
120
|
+
副次発見: Desktop の幽霊セッション(/Users/kite)も SessionEnd(other) を高頻度で発火する。
|
|
121
|
+
- [x] 判定: **NO-GO**。Desktop は /clear で SessionEnd を即時発火するが reason を `other` にラベルし、**セッション削除(明示的破棄)と区別不能**。reason=other でバトンを書くと削除セッションの記憶が次セッションに蘇る誤注入 + 幽霊バトン汚染。reason 不問の退行案は不採用(計画どおり)。→ A Phase 2 は実装せず停止、fallback 裁定へ
|
|
122
|
+
- [x] spike 撤去: settings.json から SessionEnd 登録を削除(JSON 検証済み)、spike ファイル削除(git 履歴に残存)。実測ログ `~/.throughline/logs/session-end-spike.log` は証拠として保全
|
|
123
|
+
|
|
124
|
+
### A Phase 2 — 本実装(GO の場合のみ。refuter で出た穴 4 件の対策込み)
|
|
125
|
+
|
|
126
|
+
> **2026-07-12 NO-GO につき凍結**。Desktop が SessionEnd reason を正しくラベルする(または SessionStart source='clear' を送る)ようになった時点で解凍可。fallback は Phase 1 実測表とともにオーナー裁定: 案C(明文化 + /tl 運用)+ upstream 報告(証拠は二重: SessionStart source=startup 誤ラベル + SessionEnd reason=other 誤ラベル、VSCode 対照つき)。オーナー裁定 (2026-07-12): fallback 案C 採用・upstream 報告提出済み https://github.com/anthropics/claude-code/issues/76704 (修正が入れば auto path がそのまま Desktop で復活する)。
|
|
127
|
+
|
|
128
|
+
- [ ] schema v9: `handoff_batons.origin` 列(`'tl' | 'clear-prompt' | 'clear-session-end'`)【F: 統括直轄】
|
|
129
|
+
- [ ] バトン上書き規則: 明示 /tl は TTL 内なら自動バトンに上書きされない(明示意思 > 自動)。consumeBaton は origin を返し inheritance-decision.log に `baton_origin` 記録 【F: 統括直轄】
|
|
130
|
+
- [ ] `src/session-end.mjs` 新設: reason==='clear' かつ `THROUGHLINE_DISABLE_AUTO_HANDOFF !== '1'` で writeBaton。全イベントを session-end.log に記録。import-safe run() 型 【A: 実装物量 → 02_models.md:40】
|
|
131
|
+
- [ ] bin dispatch / install.mjs SC_HOOKS 追加(**per-hook timeout 明示** — 既定 1.5 秒 kill 対策)/ uninstall / doctor 表示 【A: 実装物量 → 02_models.md:40】
|
|
132
|
+
- [ ] テスト: baton origin 規則・session-end subprocess・db-schema v9・install 冪等 【A: 実装物量 → 02_models.md:40 → 統括 diff レビュー + ゲート再実行】
|
|
133
|
+
- [ ] TTL は /tl と同じ 1 時間(一貫性優先。短縮代替案: clear 由来のみ 5〜10 分に絞る案があったが、Desktop は後継生成が初回プロンプト時なので取りこぼしリスクと引き換え=不採用の記録)
|
|
134
|
+
|
|
135
|
+
### A Phase 3 — E2E・後始末
|
|
136
|
+
|
|
137
|
+
- [x] spike hook 撤去(settings.json 復元確認)
|
|
138
|
+
- [x] E2E(2026-07-12 実機・緑): Desktop `/tl`(前任 6c58be18)→ `/clear` → 後継 e8bb5bd3 が `triggered_path:"baton"`・merged:true。`backfill.log` に `hook:"session-start" origin:6c58be18`。後継 bodies に前任の全ターン(turn 1/3/5/8、`/tl` 直前の turn 5 含む)。後継モデルが「前のセッションから記憶を引き継いだ」と自覚。→ 0.6.0 リリースへ
|
|
139
|
+
- [x] `npm test` 全緑、CLAUDE.md / README / docs 更新、caveat_update で `claude-code-desktop-clear-sessionstart-source-startup-throughline` の resolution 更新(2026-07-12 更新済み)
|
|
140
|
+
|
|
141
|
+
## Workstream B-2 — Desktop transcript 本文欠落の条件特定(調査のみ。実装なし)
|
|
142
|
+
|
|
143
|
+
- 2026-07-12 追試 — 本調査セッション自身で欠落が継続再現(15:20 以降の本文 ~8 個中 5 個のみ着地・中間分析テキストが欠落)。短ターン(オーナーのテストセッション 4 本)は全て着地 → 「長い tool 連発ターンで欠ける」仮説と整合。保存構造棚卸しは Claude レーンで実行中(会話実データを外部枠に流さないプライバシー優先の逸脱 — 02_models.md:36 の既定から明示逸脱)。
|
|
144
|
+
- [x] 最小再現・条件特定(2026-07-12): 短ターン(1 往復)は本文が必ず着地(オーナーのテストセッション 4 本 + 過去実績)。欠落は**長い tool 連発ターンの中間テキスト**で発生し、同一セッションで 2 回ライブ再現(12:44-13:15 は本文 8 個中 1 個のみ・16 分遅延着地/15:20-15:35 は ~8 個中 5 個)。厳密な断片選択規則は Desktop 内部実装依存で外部から特定不能と判断
|
|
145
|
+
- [x] 保存構造の read-only 棚卸し(Claude レーン Explore agent。会話実データを外部枠に流さないプライバシー優先の逸脱): **Desktop の App Support ストア(LevelDB / IndexedDB / SQLite / session JSON)は会話本文を一切保持しない**。3 エンコーディング全域走査で、JSONL に実在する対照プローブすら 0 件。session JSON はメタデータのみ。→ `~/.claude/projects/*.jsonl` が唯一のローカル本文ストアで、**書かれなかった本文はサーバ側のみ=ローカル回収経路なし**(確度: 高)
|
|
146
|
+
- [x] 結論を caveat(`claude-code-desktop-assistant-transcript-jsonl`、confidence: reproduced へ更新)と本文書に記録
|
|
147
|
+
- [x] **upstream 報告 #2 提出済み**(2026-07-12・オーナー承認済み): [anthropics/claude-code#76706](https://github.com/anthropics/claude-code/issues/76706)(長い tool 連発ターンで assistant text ブロックが transcript JSONL に永久欠落・ローカル回収経路なし・VS Code は正常)
|
|
148
|
+
|
|
149
|
+
## 実装しないこと
|
|
150
|
+
|
|
151
|
+
- 案A startup 時間窓フォールバック / reason 不問バトン / prompt-submit の /clear 分岐削除(無害残置)
|
|
152
|
+
- B-2 の「修正」実装(原因が CC 側なら Throughline では直せない。B-1 のバックフィルが Throughline 側でできる最大限)
|
|
153
|
+
|
|
154
|
+
## 検証コマンド
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npm test
|
|
158
|
+
node bin/throughline.mjs doctor
|
|
159
|
+
tail -f ~/.throughline/logs/session-end-spike.log # A Phase 1
|
|
160
|
+
tail -f ~/.throughline/logs/inheritance-decision.log # A Phase 3 (baton_origin)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 付録 — 欠落率調査スクリプト(B-1 検証用・read-only)
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
// node --input-type=module < この内容 (リポジトリルートで実行)
|
|
167
|
+
import { getDb } from './src/db.mjs';
|
|
168
|
+
import { readTranscript } from './src/transcript-reader.mjs';
|
|
169
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
170
|
+
import { join } from 'node:path';
|
|
171
|
+
import { homedir } from 'node:os';
|
|
172
|
+
|
|
173
|
+
const db = getDb();
|
|
174
|
+
const sessions = db.prepare(`
|
|
175
|
+
SELECT session_id, project_path FROM sessions
|
|
176
|
+
WHERE merged_into IS NULL AND session_id NOT LIKE 'codex:%'
|
|
177
|
+
ORDER BY updated_at DESC LIMIT 300
|
|
178
|
+
`).all();
|
|
179
|
+
const projRoot = join(homedir(), '.claude', 'projects');
|
|
180
|
+
const tPath = (p, sid) => {
|
|
181
|
+
const f = join(projRoot, p.replace(/[\/.]/g, '-').replace(/^-?/, '-'), sid + '.jsonl');
|
|
182
|
+
return existsSync(f) ? f : null;
|
|
183
|
+
};
|
|
184
|
+
const logicalTurns = (turns) => {
|
|
185
|
+
const r = []; let cur = null;
|
|
186
|
+
for (let i = 0; i < turns.length; i++) {
|
|
187
|
+
if (turns[i].role === 'user') { if (cur) r.push(cur); cur = { lastAsstIdx: -1 }; }
|
|
188
|
+
else if (turns[i].role === 'assistant' && cur) cur.lastAsstIdx = i;
|
|
189
|
+
}
|
|
190
|
+
if (cur) r.push(cur);
|
|
191
|
+
return r.filter(lt => lt.lastAsstIdx >= 0);
|
|
192
|
+
};
|
|
193
|
+
const byClient = {};
|
|
194
|
+
for (const s of sessions) {
|
|
195
|
+
if (s.project_path.includes('haiku-workdir')) continue; // 再帰ガードで捕捉しない設計
|
|
196
|
+
const tp = tPath(s.project_path, s.session_id);
|
|
197
|
+
if (!tp) continue;
|
|
198
|
+
const lts = logicalTurns(readTranscript(tp));
|
|
199
|
+
if (lts.length < 2) continue;
|
|
200
|
+
let ep = null;
|
|
201
|
+
for (const line of readFileSync(tp, 'utf8').split('\n')) {
|
|
202
|
+
try { const e = JSON.parse(line); if (e.entrypoint) { ep = e.entrypoint; break; } } catch {}
|
|
203
|
+
}
|
|
204
|
+
if (!ep) continue;
|
|
205
|
+
const cap = new Set(db.prepare(
|
|
206
|
+
`SELECT turn_number FROM bodies WHERE origin_session_id = ? AND role = 'assistant'`
|
|
207
|
+
).all(s.session_id).map(r => r.turn_number));
|
|
208
|
+
const scanned = lts.slice(0, -1); // 最終ターンは進行中の可能性 → 除外
|
|
209
|
+
const miss = scanned.filter(lt => !cap.has(lt.lastAsstIdx)).length;
|
|
210
|
+
byClient[ep] ??= { sessions: 0, turns: 0, missing: 0 };
|
|
211
|
+
byClient[ep].sessions++; byClient[ep].turns += scanned.length; byClient[ep].missing += miss;
|
|
212
|
+
}
|
|
213
|
+
for (const [k, v] of Object.entries(byClient))
|
|
214
|
+
console.log(k, v, `${(100 * v.missing / v.turns).toFixed(1)}%`);
|
|
215
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ADR 0001: Keep Claude Primary, Add Codex as Adapter
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-04
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Throughline grew as a Claude Code hooks plugin. Its core behavior depends on Claude-facing hooks, slash commands, transcript parsing, and `/clear` / `/tl` handoff semantics. Codex support is valuable, but replacing Claude contracts would break the existing product boundary and historical guarantees.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
Keep Claude Code behavior first-class. Add Codex support through adapter/projection layers such as `throughline_handoff`, Codex rollout capture, Codex CLI summarization, and explicit diagnostic trim surfaces. Do not rename, remove, or implicitly replace Claude-facing fields, command names, hook shapes, transcript contracts, or handoff semantics for Codex support.
|
|
16
|
+
|
|
17
|
+
## Consequences
|
|
18
|
+
|
|
19
|
+
- Claude hooks and slash commands remain the compatibility baseline.
|
|
20
|
+
- Codex support must fail explicitly when required host primitives or captured DB memory are unavailable.
|
|
21
|
+
- Agent-neutral core may grow only behind stable Claude-compatible projections.
|
|
22
|
+
- Future docs should update the numbered canonical docs before changing README-facing behavior.
|
package/docs/archive/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
このフォルダの内容は **歴史的資料**。現行実装を説明していない。
|
|
4
4
|
|
|
5
|
-
現行の設計仕様は一つ上のディレクトリの [
|
|
5
|
+
現行の設計仕様は一つ上のディレクトリの [01_l1_l2_l3_redesign.md](../01_l1_l2_l3_redesign.md) を参照。
|
|
6
6
|
|
|
7
7
|
## このフォルダにあるもの
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| CONCEPT.md | Throughline の初期コンセプト文書。L2 = 判断 (judgment) 抽出という構造化方式を想定していた | schema v4 で judgments テーブル廃止、L2 は「会話本文そのまま」に再定義。本文書の L2 節以降は実装と乖離している |
|
|
12
12
|
| EXPERIMENT.md | `/clear` 跨ぎで旧/新 session_id を紐づけるための命題 A〜X と実機検証の記録 | 結論として記憶張り替え方式 (merged_into + origin_session_id) が採用され、本実験結果は歴史記録としてのみ価値がある |
|
|
13
13
|
| SESSION_LINKING_DESIGN.md | 命題 X(ファイルベース紐付け)の実装設計書。時間窓+ state ファイル方式 | 同上。最終的に記憶張り替え方式に置き換えられ、spike コードも破棄済み |
|
|
14
|
-
| THROUGHLINE_NEXT_STEPS.md | 2026-04-17、npm publish 直前の優先順位メモ(publish 済ませろ / awesome-claude-code 登録 / HN 投稿)| npm publish は v0.1.0 〜 v0.3.x まで複数回実施、awesome-claude-code は未提出。現在の未完タスクは [../
|
|
14
|
+
| THROUGHLINE_NEXT_STEPS.md | 2026-04-17、npm publish 直前の優先順位メモ(publish 済ませろ / awesome-claude-code 登録 / HN 投稿)| npm publish は v0.1.0 〜 v0.3.x まで複数回実施、awesome-claude-code は未提出。現在の未完タスクは [../04_public_release_plan.md](../04_public_release_plan.md) に集約 |
|
|
15
15
|
|
|
16
16
|
## なぜアーカイブするか
|
|
17
17
|
|
|
@@ -19,4 +19,4 @@
|
|
|
19
19
|
- 具体的なスキーマ、層の中身、フック構成はすべて実装中に改訂された
|
|
20
20
|
- 最新の正と歴史が同じフォルダに並ぶと読み手が混乱する
|
|
21
21
|
|
|
22
|
-
新規に仕様を読む場合は必ず [../
|
|
22
|
+
新規に仕様を読む場合は必ず [../01_l1_l2_l3_redesign.md](../01_l1_l2_l3_redesign.md) から始めること。
|
|
@@ -47,7 +47,7 @@ README に `npm install -g throughline` と書いてあるが、現時点で npm
|
|
|
47
47
|
|
|
48
48
|
- `package.json` の `version` が `0.1.0`。最初の publish はこれでOK。以降は semver 守って上げる
|
|
49
49
|
- publish した瞬間に取り消せない (unpublish には制約あり)。dry-run は真剣にやる
|
|
50
|
-
- docs/
|
|
50
|
+
- docs/04_public_release_plan.md §0 の「フォールバック禁止」原則に従い、install 失敗時は silent 処理しない
|
|
51
51
|
|
|
52
52
|
### 🟡 中優先 — awesome-claude-code に登録申請
|
|
53
53
|
|
|
@@ -104,8 +104,8 @@ codeburn / ccburn などの類似ツールがここに載ってる。Throughline
|
|
|
104
104
|
## 作業指針
|
|
105
105
|
|
|
106
106
|
1. **手を動かす前に READ しろ**
|
|
107
|
-
- `docs/
|
|
108
|
-
- `docs/
|
|
107
|
+
- `docs/01_l1_l2_l3_redesign.md` (認証の設計書)
|
|
108
|
+
- `docs/04_public_release_plan.md` (§0 ルールと未完タスクの定義)
|
|
109
109
|
- `CLAUDE.md` (作業上の規律)
|
|
110
110
|
2. **§0 ルールを厳守**
|
|
111
111
|
- silent try/catch 禁止
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "throughline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)",
|
|
6
6
|
"keywords": [
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
".claude/commands/",
|
|
26
26
|
".codex-sidecar.yml",
|
|
27
27
|
"docs/",
|
|
28
|
+
"rag/",
|
|
28
29
|
"CHANGELOG.md",
|
|
29
30
|
"README.md",
|
|
30
31
|
"LICENSE"
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Claude Code Hooks Reference — Extract
|
|
2
|
+
|
|
3
|
+
Source: <https://code.claude.com/docs/en/hooks> (fetched 2026-05-24)
|
|
4
|
+
|
|
5
|
+
## Hook events (31 total)
|
|
6
|
+
|
|
7
|
+
### Per-Session
|
|
8
|
+
|
|
9
|
+
- `SessionStart` — session begins or resumes
|
|
10
|
+
- `Setup` — when launched with `--init-only` / `--init` / `--maintenance`
|
|
11
|
+
- `SessionEnd` — session terminates
|
|
12
|
+
|
|
13
|
+
### Per-Turn
|
|
14
|
+
|
|
15
|
+
- `UserPromptSubmit` — user submits a prompt, before Claude processes it
|
|
16
|
+
- `UserPromptExpansion` — when a user-typed command expands into a prompt
|
|
17
|
+
- `Stop` — Claude finishes responding
|
|
18
|
+
- `StopFailure` — turn ends due to API error
|
|
19
|
+
|
|
20
|
+
### Per-Tool-Call
|
|
21
|
+
|
|
22
|
+
- `PreToolUse` — before tool call
|
|
23
|
+
- `PostToolUse` — after tool call succeeds
|
|
24
|
+
- `PostToolUseFailure` — after tool call fails
|
|
25
|
+
- `PostToolBatch` — after parallel tool batch resolves
|
|
26
|
+
- `PermissionRequest` — when permission dialog appears
|
|
27
|
+
- `PermissionDenied` — denied by auto mode classifier
|
|
28
|
+
|
|
29
|
+
### Agent/Team
|
|
30
|
+
|
|
31
|
+
- `SubagentStart` / `SubagentStop`
|
|
32
|
+
- `TaskCreated` / `TaskCompleted`
|
|
33
|
+
- `TeammateIdle`
|
|
34
|
+
|
|
35
|
+
### File/Config
|
|
36
|
+
|
|
37
|
+
- `InstructionsLoaded` — when CLAUDE.md / `.claude/rules/*.md` loaded
|
|
38
|
+
- `ConfigChange`
|
|
39
|
+
- `FileChanged`
|
|
40
|
+
- `CwdChanged`
|
|
41
|
+
- `WorktreeCreate` / `WorktreeRemove`
|
|
42
|
+
|
|
43
|
+
### Compaction
|
|
44
|
+
|
|
45
|
+
- `PreCompact` — before context compaction
|
|
46
|
+
- `PostCompact` — after context compaction
|
|
47
|
+
|
|
48
|
+
### MCP
|
|
49
|
+
|
|
50
|
+
- `Elicitation` / `ElicitationResult`
|
|
51
|
+
|
|
52
|
+
### Notification
|
|
53
|
+
|
|
54
|
+
- `Notification`
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Common input payload (all events)
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"session_id": "abc123",
|
|
63
|
+
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
|
|
64
|
+
"cwd": "/home/user/my-project",
|
|
65
|
+
"permission_mode": "default|plan|acceptEdits|auto|dontAsk|bypassPermissions",
|
|
66
|
+
"hook_event_name": "PreToolUse",
|
|
67
|
+
"effort": { "level": "low|medium|high|xhigh|max" },
|
|
68
|
+
"agent_id": "optional-subagent-id",
|
|
69
|
+
"agent_type": "optional-agent-name"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### SessionStart input
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"session_id": "abc123",
|
|
78
|
+
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
|
|
79
|
+
"cwd": "/Users/...",
|
|
80
|
+
"hook_event_name": "SessionStart",
|
|
81
|
+
"source": "startup|resume|clear|compact",
|
|
82
|
+
"model": "claude-sonnet-4-6"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### UserPromptSubmit input
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"session_id": "abc123",
|
|
91
|
+
"transcript_path": "...",
|
|
92
|
+
"cwd": "/Users/...",
|
|
93
|
+
"permission_mode": "default",
|
|
94
|
+
"hook_event_name": "UserPromptSubmit",
|
|
95
|
+
"prompt": "Write a function to calculate the factorial of a number"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## hookSpecificOutput
|
|
102
|
+
|
|
103
|
+
Common shape:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"hookSpecificOutput": {
|
|
108
|
+
"hookEventName": "PostToolUse",
|
|
109
|
+
"additionalContext": "text string to inject into Claude's context"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### What `additionalContext` actually does (CRITICAL)
|
|
115
|
+
|
|
116
|
+
> The `additionalContext` field passes a string from your hook into Claude's context window. **Claude Code wraps the string in a system reminder** and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it does not appear as a chat message in the interface.
|
|
117
|
+
|
|
118
|
+
**Placement by event:**
|
|
119
|
+
|
|
120
|
+
| Event Category | Placement |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `SessionStart`, `Setup`, `SubagentStart` | At the start of the conversation, before the first prompt |
|
|
123
|
+
| `UserPromptSubmit`, `UserPromptExpansion` | Alongside the submitted prompt |
|
|
124
|
+
| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch` | Next to the tool result |
|
|
125
|
+
|
|
126
|
+
**Character limit:** 10,000 characters per context string. Excess → written to file in session dir, file path + preview passed to Claude.
|
|
127
|
+
|
|
128
|
+
**Content type:** delivered AS A SYSTEM REMINDER (not user message). Same category as stdout.
|
|
129
|
+
|
|
130
|
+
### SessionStart hookSpecificOutput (THE KEY DISCOVERY)
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"hookSpecificOutput": {
|
|
135
|
+
"hookEventName": "SessionStart",
|
|
136
|
+
"additionalContext": "context string",
|
|
137
|
+
"initialUserMessage": "first message (SessionStart only)",
|
|
138
|
+
"watchPaths": ["/path/to/watch1", "/path/to/watch2"]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
🎯 **`initialUserMessage` is a SessionStart-only field that becomes the first user message of the session.** This goes into `messages[]` as a real user-role turn, NOT a system reminder.
|
|
144
|
+
|
|
145
|
+
This is the unexplored angle for Throughline: instead of injecting context (system reminder = "briefing"), inject the resume context AS the first user message ("the user just said this, including past history") so the model treats it as actual conversation input.
|
|
146
|
+
|
|
147
|
+
### UserPromptSubmit hookSpecificOutput
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"hookSpecificOutput": {
|
|
152
|
+
"hookEventName": "UserPromptSubmit",
|
|
153
|
+
"additionalContext": "context string",
|
|
154
|
+
"sessionTitle": "auto-set session title"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
(No `initialUserMessage` — it only exists on SessionStart.)
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Hook output handling
|
|
164
|
+
|
|
165
|
+
| Exit Code | Meaning | JSON Processed? | Blocking? |
|
|
166
|
+
|---|---|---|---|
|
|
167
|
+
| **0** | Success | YES (if JSON present) | No |
|
|
168
|
+
| **2** | Blocking error | NO (JSON ignored) | Yes (event-dependent) |
|
|
169
|
+
| Other | Non-blocking error | NO | No |
|
|
170
|
+
|
|
171
|
+
### Stdout
|
|
172
|
+
|
|
173
|
+
| Event | stdout becomes Claude-visible? |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `SessionStart` / `UserPromptSubmit` / `UserPromptExpansion` | YES (as system reminder) |
|
|
176
|
+
| Other events | NO (debug log only) |
|
|
177
|
+
|
|
178
|
+
### Resume / replay behavior
|
|
179
|
+
|
|
180
|
+
> Once injected, the text is saved in the session transcript. For mid-session events like `PostToolUse` or `UserPromptSubmit`, resuming with `--continue` or `--resume` replays the saved text rather than re-running the hook for past turns, so values like timestamps or commit SHAs become stale on resume. **`SessionStart` hooks run again on resume with `source` set to `"resume"`, so they can refresh their context.**
|
|
181
|
+
|
|
182
|
+
### JSON output fields (exit 0 only)
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"continue": true,
|
|
187
|
+
"stopReason": "message if continue is false",
|
|
188
|
+
"suppressOutput": false,
|
|
189
|
+
"systemMessage": "warning shown to user",
|
|
190
|
+
"terminalSequence": "OSC escape sequence",
|
|
191
|
+
"hookSpecificOutput": {
|
|
192
|
+
"hookEventName": "EventName",
|
|
193
|
+
"additionalContext": "..."
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
| Field | Default | Effect |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `continue` | `true` | If `false`, Claude stops processing entirely |
|
|
201
|
+
| `stopReason` | — | Message shown when `continue: false` |
|
|
202
|
+
| `suppressOutput` | `false` | Hide hook's stdout from transcript (still in debug log) |
|
|
203
|
+
| `systemMessage` | — | Warning shown to user |
|
|
204
|
+
| `terminalSequence` | — | OSC 0/1/2/9/99/777, BEL only |
|
|
205
|
+
|
|
206
|
+
### PreToolUse decision control (extra)
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"hookSpecificOutput": {
|
|
211
|
+
"hookEventName": "PreToolUse",
|
|
212
|
+
"permissionDecision": "allow|deny|ask|defer",
|
|
213
|
+
"permissionDecisionReason": "explanation text",
|
|
214
|
+
"additionalContext": "context for Claude",
|
|
215
|
+
"updatedInput": { "command": "modified command" }
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`updatedInput` modifies tool parameters before execution. Note: only PreToolUse has `updatedInput`.
|
|
221
|
+
|
|
222
|
+
### When multiple hooks return additionalContext
|
|
223
|
+
|
|
224
|
+
> When several hooks return `additionalContext` for the same event, Claude receives all of the values.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Transcript / messages[] construction (incompletely documented)
|
|
229
|
+
|
|
230
|
+
The docs do NOT detail how messages[] is constructed from JSONL. What we can infer:
|
|
231
|
+
|
|
232
|
+
1. Hook context = system reminders (not user/assistant messages)
|
|
233
|
+
2. Stdout = system reminders for SessionStart / UserPromptSubmit / UserPromptExpansion, log-only otherwise
|
|
234
|
+
3. Hook outputs are persisted in transcript and replayed on `--continue` / `--resume`
|
|
235
|
+
4. SessionStart re-runs on resume with `source: "resume"` (so it can refresh)
|
|
236
|
+
5. **`initialUserMessage`** appears to be the only documented way for a hook to inject a real user-role message
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Output method summary
|
|
241
|
+
|
|
242
|
+
| Method | Exit | Content Type | Visible to Claude? | Use Case |
|
|
243
|
+
|---|---|---|---|---|
|
|
244
|
+
| Plain stdout (SessionStart / UserPromptSubmit / UserPromptExpansion) | 0 | Text | YES (system reminder) | Quick context injection |
|
|
245
|
+
| Plain stdout (other) | 0 | Text | NO (debug log) | Logging only |
|
|
246
|
+
| Exit code 2 | 2 | stderr | YES (as error) | Policy enforcement |
|
|
247
|
+
| JSON `additionalContext` | 0 | JSON | YES (system reminder) | Structured context |
|
|
248
|
+
| **JSON `initialUserMessage` (SessionStart only)** | 0 | JSON | **YES (as user message!)** | **First user-role injection** |
|
|
249
|
+
| JSON `decision: "block"` | 0 | JSON | YES (decision reason) | Event-specific block |
|
|
250
|
+
| JSON `continue: false` | 0 | JSON | Halts session | Hard stop |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Claude Code Hooks — SessionEnd reason enum (Extract)
|
|
2
|
+
|
|
3
|
+
Source: <https://code.claude.com/docs/en/hooks> (fetched 2026-07-11)
|
|
4
|
+
確度: 公式 docs verbatim(Matcher patterns テーブルより)。実機検証は Throughline docs/12 A Phase 1 spike で実施予定。
|
|
5
|
+
|
|
6
|
+
## SessionEnd
|
|
7
|
+
|
|
8
|
+
- SessionEnd イベントの matcher は「why the session ended」でフィルタする。
|
|
9
|
+
- **reason enum**: `clear` / `resume` / `logout` / `prompt_input_exit` / `bypass_permissions_disabled` / `other`
|
|
10
|
+
- `clear` = "Session cleared with /clear command"
|
|
11
|
+
- **デフォルト timeout は 1.5 秒**で /clear にも適用される(hook が 1.5 秒を超えるとサイレント kill)→ 登録時は per-hook timeout を明示すること。
|
|
12
|
+
|
|
13
|
+
## SessionStart source(同 fetch での再確認)
|
|
14
|
+
|
|
15
|
+
- `source` enum: `"startup"`(新規)/ `"resume"` / `"clear"`(/clear 後)/ `"compact"`
|
|
16
|
+
|
|
17
|
+
## Throughline 的含意(実測とセット)
|
|
18
|
+
|
|
19
|
+
- VSCode(2.1.195〜2.1.207 実測)は /clear で `source:"clear"` を送る。Desktop(2.1.205 実測)は `source:"startup"` を送る=クライアント実装差(docs/12 §2)。
|
|
20
|
+
- ビルトイン /clear は UserPromptSubmit / UserPromptExpansion のどちらにも乗らない(UserPromptExpansion の対象は skill / custom command / mcp_prompt のみ)= /clear 検知は SessionEnd(reason='clear') が唯一の hook 経路候補。
|
|
21
|
+
- Desktop が SessionEnd(reason='clear') を実際に発火するかは未検証(2026-07-11 時点)。
|