Skip to content

Commit 8087c9a

Browse files
committed
feat: surface hook metadata & tool result JSON; fix PreToolUse attachment dropped by live-chain filter; skip empty redacted thinking items
- Fix: attachment entries (hook results) were silently dropped by the live-chain UUID filter in read_session_incremental — their uuid is never referenced as anyone's parentUuid, so they never entered the live set. Now passed through when parentUuid is on the live chain. - feat: extract all key-value pairs from hook attachments as hook_metadata (Option<Value>) and from tool results as tool_result_json (Option<Value>); propagated through HookMsg → ContentBlock → DisplayItem → FrontendDisplayItem → shared/types.ts - fix: skip Thinking DisplayItems when thinking text is empty (extended thinking blocks store only an encrypted signature in JSONL; thinking_count badge still reflects the correct count) - chore: bump version 0.4.12 → 0.4.13
1 parent f7eb799 commit 8087c9a

18 files changed

Lines changed: 145 additions & 31 deletions

AGENTS.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# GitNexus — Code Intelligence
44

5-
This project is indexed by GitNexus as **cct-626ddc17** (1747 symbols, 4138 relationships, 146 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
5+
This project is indexed by GitNexus as **tail-claude-gui** (1766 symbols, 4194 relationships, 148 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
66

77
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
88
@@ -18,7 +18,7 @@ This project is indexed by GitNexus as **cct-626ddc17** (1747 symbols, 4138 rela
1818

1919
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2020
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
21-
3. `READ gitnexus://repo/cct-626ddc17/process/{processName}` — trace the full execution flow step by step
21+
3. `READ gitnexus://repo/tail-claude-gui/process/{processName}` — trace the full execution flow step by step
2222
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
2323

2424
## When Refactoring
@@ -55,12 +55,12 @@ This project is indexed by GitNexus as **cct-626ddc17** (1747 symbols, 4138 rela
5555

5656
## Resources
5757

58-
| Resource | Use for |
59-
| --------------------------------------------- | ---------------------------------------- |
60-
| `gitnexus://repo/cct-626ddc17/context` | Codebase overview, check index freshness |
61-
| `gitnexus://repo/cct-626ddc17/clusters` | All functional areas |
62-
| `gitnexus://repo/cct-626ddc17/processes` | All execution flows |
63-
| `gitnexus://repo/cct-626ddc17/process/{name}` | Step-by-step execution trace |
58+
| Resource | Use for |
59+
| ------------------------------------------------ | ---------------------------------------- |
60+
| `gitnexus://repo/tail-claude-gui/context` | Codebase overview, check index freshness |
61+
| `gitnexus://repo/tail-claude-gui/clusters` | All functional areas |
62+
| `gitnexus://repo/tail-claude-gui/processes` | All execution flows |
63+
| `gitnexus://repo/tail-claude-gui/process/{name}` | Step-by-step execution trace |
6464

6565
## Self-Check Before Finishing
6666

CLAUDE.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ npx oxfmt && npx oxlint && npx tsc --noEmit && npx vitest run && cargo fmt --man
3838

3939
# GitNexus — Code Intelligence
4040

41-
This project is indexed by GitNexus as **cct-626ddc17** (1747 symbols, 4138 relationships, 146 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
41+
This project is indexed by GitNexus as **tail-claude-gui** (1766 symbols, 4194 relationships, 148 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
4242

4343
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
4444
@@ -54,7 +54,7 @@ This project is indexed by GitNexus as **cct-626ddc17** (1747 symbols, 4138 rela
5454

5555
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
5656
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
57-
3. `READ gitnexus://repo/cct-626ddc17/process/{processName}` — trace the full execution flow step by step
57+
3. `READ gitnexus://repo/tail-claude-gui/process/{processName}` — trace the full execution flow step by step
5858
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
5959

6060
## When Refactoring
@@ -91,12 +91,12 @@ This project is indexed by GitNexus as **cct-626ddc17** (1747 symbols, 4138 rela
9191

9292
## Resources
9393

94-
| Resource | Use for |
95-
| --------------------------------------------- | ---------------------------------------- |
96-
| `gitnexus://repo/cct-626ddc17/context` | Codebase overview, check index freshness |
97-
| `gitnexus://repo/cct-626ddc17/clusters` | All functional areas |
98-
| `gitnexus://repo/cct-626ddc17/processes` | All execution flows |
99-
| `gitnexus://repo/cct-626ddc17/process/{name}` | Step-by-step execution trace |
94+
| Resource | Use for |
95+
| ------------------------------------------------ | ---------------------------------------- |
96+
| `gitnexus://repo/tail-claude-gui/context` | Codebase overview, check index freshness |
97+
| `gitnexus://repo/tail-claude-gui/clusters` | All functional areas |
98+
| `gitnexus://repo/tail-claude-gui/processes` | All execution flows |
99+
| `gitnexus://repo/tail-claude-gui/process/{name}` | Step-by-step execution trace |
100100

101101
## Self-Check Before Finishing
102102

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claude-code-trace",
3-
"version": "0.4.12",
3+
"version": "0.4.13",
44
"private": true,
55
"bin": {
66
"cctrace": "./bin/cctrace.mjs"

shared/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ export interface DisplayItem {
5555
hook_event: string;
5656
hook_name: string;
5757
hook_command: string;
58+
/** All key-value pairs from the hook attachment JSON (pretty-printed). */
59+
hook_metadata: string;
60+
/** Tool result as pretty-printed JSON when the content is an object or array. */
61+
tool_result_json: string;
5862
is_orphan: boolean;
5963
}
6064

src-tauri/Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "claude-code-trace"
3-
version = "0.4.12"
3+
version = "0.4.13"
44
edition = "2021"
55
rust-version = "1.77.2"
66

src-tauri/src/convert.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ pub struct FrontendDisplayItem {
3939
pub hook_event: String,
4040
pub hook_name: String,
4141
pub hook_command: String,
42+
/// All key-value pairs from the hook attachment JSON (pretty-printed).
43+
pub hook_metadata: String,
44+
/// Tool result as pretty-printed JSON when the content is an object or array.
45+
pub tool_result_json: String,
4246
pub is_orphan: bool,
4347
pub subagent_prompt: String,
4448
pub is_deferred: bool,
@@ -247,6 +251,8 @@ fn convert_display_items(
247251
hook_event: it.hook_event.clone(),
248252
hook_name: it.hook_name.clone(),
249253
hook_command: it.hook_command.clone(),
254+
hook_metadata: pretty_json(&it.hook_metadata),
255+
tool_result_json: pretty_json(&it.tool_result_json),
250256
is_orphan: it.is_orphan,
251257
subagent_prompt: String::new(),
252258
is_deferred: it.is_deferred,

src-tauri/src/parser/chunk.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ pub struct DisplayItem {
4242
pub hook_event: String,
4343
pub hook_name: String,
4444
pub hook_command: String,
45+
/// All key-value pairs from the hook attachment JSON.
46+
pub hook_metadata: Option<serde_json::Value>,
47+
/// Tool result parsed as a JSON value when the content is an object or array.
48+
pub tool_result_json: Option<serde_json::Value>,
4549
pub is_orphan: bool,
4650
/// True when the session was suspended via a "defer" PreToolUse permission decision
4751
/// before a tool_result arrived for this tool_use block.
@@ -70,6 +74,8 @@ impl Default for DisplayItem {
7074
hook_event: String::new(),
7175
hook_name: String::new(),
7276
hook_command: String::new(),
77+
hook_metadata: None,
78+
tool_result_json: None,
7379
is_orphan: false,
7480
is_deferred: false,
7581
}
@@ -198,6 +204,7 @@ pub fn build_chunks(msgs: &[ClassifiedMsg]) -> Vec<Chunk> {
198204
text: m.command.clone(),
199205
tool_name: m.hook_name.clone(),
200206
tool_id: m.hook_event.clone(),
207+
hook_metadata: m.metadata.clone(),
201208
..Default::default()
202209
}],
203210
usage: Usage::default(),
@@ -258,11 +265,17 @@ fn merge_ai_buffer(buf: &[AIMsg]) -> Chunk {
258265
for b in &m.blocks {
259266
match b.block_type.as_str() {
260267
"thinking" => {
261-
items.push(DisplayItem {
262-
item_type: DisplayItemType::Thinking,
263-
text: b.text.clone(),
264-
..Default::default()
265-
});
268+
// Extended thinking blocks store an encrypted signature in the JSONL
269+
// but redact the actual text (thinking field is ""). Only emit a
270+
// Thinking DisplayItem when there is real content to show; thinking_count
271+
// already tracks the presence of thinking blocks regardless.
272+
if !b.text.is_empty() {
273+
items.push(DisplayItem {
274+
item_type: DisplayItemType::Thinking,
275+
text: b.text.clone(),
276+
..Default::default()
277+
});
278+
}
266279
}
267280
"text" => {
268281
items.push(DisplayItem {
@@ -318,6 +331,7 @@ fn merge_ai_buffer(buf: &[AIMsg]) -> Chunk {
318331
"tool_result" => {
319332
if let Some(p) = pending.remove(&b.tool_id) {
320333
items[p.index].tool_result = b.content.clone();
334+
items[p.index].tool_result_json = b.content_json.clone();
321335
items[p.index].tool_error = b.is_error;
322336
let dur = m.timestamp.signed_duration_since(p.timestamp);
323337
items[p.index].duration_ms = dur.num_milliseconds();
@@ -344,6 +358,7 @@ fn merge_ai_buffer(buf: &[AIMsg]) -> Chunk {
344358
hook_event: b.tool_id.clone(),
345359
hook_name: b.tool_name.clone(),
346360
hook_command: b.text.clone(),
361+
hook_metadata: b.hook_metadata.clone(),
347362
..Default::default()
348363
});
349364
}

src-tauri/src/parser/classify.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ pub struct ContentBlock {
4040
pub is_error: bool,
4141
pub teammate_id: String,
4242
pub teammate_color: String,
43+
/// For hook_event blocks: all key-value pairs from the hook attachment JSON.
44+
pub hook_metadata: Option<Value>,
45+
/// For tool_result blocks: raw content value before stringification.
46+
pub content_json: Option<Value>,
4347
}
4448

4549
/// Classified message types.
@@ -100,6 +104,9 @@ pub struct HookMsg {
100104
pub hook_event: String,
101105
pub hook_name: String,
102106
pub command: String,
107+
/// All key-value pairs from the hook attachment JSON (stdout, stderr, command,
108+
/// exitCode, durationMs, additionalContext, decision, reason, …).
109+
pub metadata: Option<Value>,
103110
}
104111

105112
pub const SYSTEM_OUTPUT_TAGS: &[&str] = &[
@@ -172,6 +179,7 @@ pub fn classify(e: Entry) -> Option<ClassifiedMsg> {
172179
hook_event,
173180
hook_name,
174181
command,
182+
metadata: None,
175183
}));
176184
}
177185
}
@@ -197,6 +205,7 @@ pub fn classify(e: Entry) -> Option<ClassifiedMsg> {
197205
hook_event: "Stop".to_string(),
198206
hook_name,
199207
command: String::new(),
208+
metadata: None,
200209
}));
201210
}
202211
// hook_progress: written in verbose/stream-json mode for mid-session hooks.
@@ -206,6 +215,7 @@ pub fn classify(e: Entry) -> Option<ClassifiedMsg> {
206215
hook_event: e.hook_event.clone(),
207216
hook_name: e.hook_name.clone(),
208217
command: String::new(),
218+
metadata: None,
209219
}));
210220
}
211221
// hookEvent present on any system entry (forward-compat for future hook types).
@@ -215,6 +225,7 @@ pub fn classify(e: Entry) -> Option<ClassifiedMsg> {
215225
hook_event: e.hook_event.clone(),
216226
hook_name: e.hook_name.clone(),
217227
command: String::new(),
228+
metadata: None,
218229
}));
219230
}
220231
_ => {}
@@ -253,11 +264,17 @@ pub fn classify(e: Entry) -> Option<ClassifiedMsg> {
253264
}
254265
.trim()
255266
.to_string();
267+
// Store the whole attachment object so all key-value pairs are available:
268+
// type, hookName, hookEvent, command, stdout, stderr, exitCode, durationMs,
269+
// additionalContext (when stdout is parsed JSON), decision, reason, etc.
270+
// Any nested JSON strings (e.g. stdout) are left for the frontend to expand.
271+
let metadata = Some(att.clone());
256272
return Some(ClassifiedMsg::Hook(HookMsg {
257273
timestamp: ts,
258274
hook_event: hook_event.to_string(),
259275
hook_name,
260276
command,
277+
metadata,
261278
}));
262279
}
263280
}
@@ -300,6 +317,7 @@ pub fn classify(e: Entry) -> Option<ClassifiedMsg> {
300317
hook_event: "Stop".to_string(),
301318
hook_name,
302319
command,
320+
metadata: None,
303321
}));
304322
}
305323
}
@@ -677,14 +695,19 @@ fn extract_meta_blocks(content: &Option<Value>, text_fallback: &str) -> Vec<Cont
677695
.and_then(|v| v.as_str())
678696
.unwrap_or("")
679697
.to_string();
680-
let raw_content = stringify_content(&b.get("content").cloned());
698+
let raw_content_val = b.get("content").cloned();
699+
let raw_content = stringify_content(&raw_content_val);
681700
let content = resolve_persisted_output(&raw_content);
682701
let is_error = b.get("is_error").and_then(|v| v.as_bool()).unwrap_or(false);
702+
// Keep the raw Value so callers can access all key-value pairs when the
703+
// tool result is a JSON object (not just the stringified form).
704+
let content_json = raw_content_val.filter(|v| v.is_object() || v.is_array());
683705
Some(ContentBlock {
684706
block_type: "tool_result".to_string(),
685707
tool_id,
686708
content,
687709
is_error,
710+
content_json,
688711
..Default::default()
689712
})
690713
})

0 commit comments

Comments
 (0)