Skip to content

Commit 99c30c1

Browse files
test: add real-session JSONL fixtures and dispatch-order regression (#52)
* test: add real-session JSONL fixtures and dispatch-order regression * test: tighten real-session fixture tests per CodeRabbit review * test: assert session title is a non-empty str in fixture shape check * test: load overlap blob from fixture and tighten minimal bash asserts Read the task_message overlap toolUseResult from real_session_all_tool_types instead of duplicating an inline dict. Pin minimal fixture bash stdout, exit code, and message index. Document JSONDecodeError skip path in malformed_lines. * test: narrow overlap fixture helper return type for mypy
1 parent 97726f4 commit 99c30c1

7 files changed

Lines changed: 579 additions & 0 deletions
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
"""One-off generator for Tuesday real_session_*.jsonl fixtures. Run from repo root:
2+
python scripts/gen_real_session_fixtures.py
3+
4+
Each entry includes top-level ``sessionId`` (real Claude Code JSONL shape). The parser
5+
currently ignores it and uses the filename for ``session_id``.
6+
"""
7+
from __future__ import annotations
8+
9+
import json
10+
import os
11+
12+
FIXTURES = os.path.join(os.path.dirname(__file__), "..", "tests", "fixtures")
13+
CWD = "/sanitized/project/path"
14+
TS = "2026-05-26T10:{:02d}:00Z"
15+
16+
# Sanitized sessionId values (one per fixture file).
17+
SESSION_IDS = {
18+
"minimal": "session-sanitized-minimal",
19+
"all_tool_types": "session-sanitized-all-tool-types",
20+
"nested_tools": "session-sanitized-nested-tools",
21+
"unknown_fields": "session-sanitized-unknown-fields",
22+
"malformed_lines": "session-sanitized-malformed-lines",
23+
}
24+
25+
26+
def _line(obj: dict) -> str:
27+
return json.dumps(obj, ensure_ascii=False) + "\n"
28+
29+
30+
def _stamp(entry: dict, session_id: str) -> dict:
31+
entry["sessionId"] = session_id
32+
return entry
33+
34+
35+
def _user(
36+
minute: int,
37+
text: str = "",
38+
tool_result: dict | None = None,
39+
*,
40+
session_id: str,
41+
sidechain: bool = False,
42+
extra: dict | None = None,
43+
) -> dict:
44+
entry: dict = {
45+
"type": "user",
46+
"timestamp": TS.format(minute),
47+
"cwd": CWD,
48+
"message": {"content": [{"type": "text", "text": text}] if text else []},
49+
}
50+
if tool_result is not None:
51+
entry["toolUseResult"] = tool_result
52+
if sidechain:
53+
entry["isSidechain"] = True
54+
if extra:
55+
entry.update(extra)
56+
return _stamp(entry, session_id)
57+
58+
59+
def _assistant(
60+
minute: int,
61+
content: list,
62+
*,
63+
session_id: str,
64+
model: str = "claude-sanitized",
65+
) -> dict:
66+
return _stamp(
67+
{
68+
"type": "assistant",
69+
"timestamp": TS.format(minute),
70+
"message": {
71+
"model": model,
72+
"content": content,
73+
"usage": {"input_tokens": 10, "output_tokens": 5},
74+
},
75+
},
76+
session_id,
77+
)
78+
79+
80+
def write_minimal() -> None:
81+
sid = SESSION_IDS["minimal"]
82+
lines = [
83+
_user(0, "Sanitized minimal real-shaped session opener", session_id=sid),
84+
_assistant(1, [{"type": "text", "text": "Acknowledged."}], session_id=sid),
85+
_user(
86+
2,
87+
tool_result={"stdout": "sanitized output\n", "stderr": "", "exitCode": 0},
88+
session_id=sid,
89+
),
90+
]
91+
path = os.path.join(FIXTURES, "real_session_minimal.jsonl")
92+
with open(path, "w", encoding="utf-8", newline="\n") as f:
93+
f.writelines(_line(x) for x in lines)
94+
95+
96+
def write_all_tool_types() -> None:
97+
# 16 tool-result user entries: 15 to cover each dispatch predicate once, plus 1
98+
# overlap blob (agentId + totalDurationMs + message) for task_message-beats-
99+
# task_completed dispatch-order regression. Total lines: 2 opener + 16 = 18 messages.
100+
sid = SESSION_IDS["all_tool_types"]
101+
tool_results = [
102+
{"stdout": "ok\n", "stderr": "", "exitCode": 0},
103+
{"filePath": f"{CWD}/a.py", "structuredPatch": "@@ sanitized"},
104+
{"filePath": f"{CWD}/b.txt", "content": "sanitized body"},
105+
{"filenames": ["sanitized.py"], "numFiles": 1, "truncated": False},
106+
{"mode": "content", "numFiles": 1, "numLines": 2, "content": "sanitized match"},
107+
{
108+
"file": {
109+
"filePath": f"{CWD}/readme.md",
110+
"numLines": 3,
111+
"content": "sanitized read",
112+
}
113+
},
114+
{"query": "sanitized query", "results": [{"url": "https://example.com/a"}]},
115+
{"url": "https://example.com/doc", "code": 200, "durationMs": 40},
116+
{"task_id": "task-sanitized-msg", "task_type": "sub"},
117+
{
118+
"retrieval_status": "found",
119+
"task": {"task_id": "task-sanitized-ret", "description": "REDACTED"},
120+
},
121+
{
122+
"agentId": "agent-sanitized-done",
123+
"totalDurationMs": 1000,
124+
"status": "completed",
125+
},
126+
{
127+
"agentId": "agent-sanitized-async",
128+
"isAsync": True,
129+
"status": "running",
130+
"description": "REDACTED background task",
131+
},
132+
{"newTodos": [{"id": "1", "content": "sanitized todo"}]},
133+
{"questions": [{"id": "q1"}], "answers": {"q1": "sanitized answer"}},
134+
{"plan": [], "filePath": f"{CWD}/plan.md"},
135+
# Dispatch-order overlap: message key present — locks task_message winning over completed
136+
{
137+
"agentId": "agent-sanitized-overlap",
138+
"totalDurationMs": 500,
139+
"status": "completed",
140+
"message": "status update sanitized",
141+
},
142+
]
143+
lines = [
144+
_user(0, "Exercise all fifteen tool-result dispatch predicates", session_id=sid),
145+
_assistant(
146+
1,
147+
[{"type": "tool_use", "id": "tu-1", "name": "Bash", "input": {"command": "echo ok"}}],
148+
session_id=sid,
149+
),
150+
]
151+
for i, tr in enumerate(tool_results):
152+
lines.append(_user(2 + i, tool_result=tr, session_id=sid))
153+
path = os.path.join(FIXTURES, "real_session_all_tool_types.jsonl")
154+
with open(path, "w", encoding="utf-8", newline="\n") as f:
155+
f.writelines(_line(x) for x in lines)
156+
157+
158+
def write_nested_tools() -> None:
159+
sid = SESSION_IDS["nested_tools"]
160+
lines = [
161+
_user(0, "Parent turn with nested subagent tool activity", session_id=sid),
162+
_assistant(
163+
1,
164+
[
165+
{
166+
"type": "tool_use",
167+
"id": "parent-tool-1",
168+
"name": "Task",
169+
"input": {"description": "sanitized subagent task"},
170+
}
171+
],
172+
session_id=sid,
173+
),
174+
_user(
175+
2,
176+
tool_result={"stdout": "sidechain output\n", "stderr": "", "exitCode": 0},
177+
session_id=sid,
178+
sidechain=True,
179+
),
180+
_stamp(
181+
{
182+
"type": "progress",
183+
"timestamp": TS.format(3),
184+
"toolUseID": "child-tool-1",
185+
"parentToolUseID": "parent-tool-1",
186+
"isSidechain": True,
187+
"data": {"type": "bash_progress", "output": "sanitized streaming chunk"},
188+
},
189+
sid,
190+
),
191+
_assistant(
192+
4,
193+
[
194+
{
195+
"type": "tool_use",
196+
"id": "nested-read-1",
197+
"name": "Read",
198+
"input": {"file_path": f"{CWD}/nested.txt"},
199+
}
200+
],
201+
session_id=sid,
202+
),
203+
]
204+
path = os.path.join(FIXTURES, "real_session_nested_tools.jsonl")
205+
with open(path, "w", encoding="utf-8", newline="\n") as f:
206+
f.writelines(_line(x) for x in lines)
207+
208+
209+
def write_unknown_fields() -> None:
210+
sid = SESSION_IDS["unknown_fields"]
211+
lines = [
212+
_user(
213+
0,
214+
"Forward-compat entry with unknown top-level keys",
215+
session_id=sid,
216+
extra={"_futureSchemaVersion": 2, "experimentalFlag": True},
217+
),
218+
_assistant(1, [{"type": "text", "text": "Handled unknown fields."}], session_id=sid),
219+
_user(
220+
2,
221+
tool_result={
222+
"stdout": "still bash\n",
223+
"stderr": "",
224+
"exitCode": 0,
225+
"_unknownToolMeta": {"vendor": "sanitized"},
226+
},
227+
session_id=sid,
228+
),
229+
]
230+
path = os.path.join(FIXTURES, "real_session_unknown_fields.jsonl")
231+
with open(path, "w", encoding="utf-8", newline="\n") as f:
232+
f.writelines(_line(x) for x in lines)
233+
234+
235+
def write_malformed_lines() -> None:
236+
sid = SESSION_IDS["malformed_lines"]
237+
valid = [
238+
_user(0, "Valid line before malformed section", session_id=sid),
239+
_assistant(1, [{"type": "text", "text": "Recovered after bad lines."}], session_id=sid),
240+
]
241+
path = os.path.join(FIXTURES, "real_session_malformed_lines.jsonl")
242+
with open(path, "w", encoding="utf-8", newline="\n") as f:
243+
for obj in valid:
244+
f.write(_line(obj))
245+
f.write("\n")
246+
f.write(
247+
"# parse_session skips lines where json.loads raises JSONDecodeError "
248+
"(blank, garbage, truncated JSON below).\n"
249+
)
250+
f.write("{not valid json\n")
251+
f.write('{"type": "user", "timestamp": "2026-05-26T10:02:00Z", "message": {"content": ')
252+
f.write("\n")
253+
f.write(
254+
_line(
255+
_user(
256+
3,
257+
"Valid line after malformed section",
258+
tool_result={"stderr": "warn only", "exitCode": 1},
259+
session_id=sid,
260+
)
261+
)
262+
)
263+
264+
265+
def main() -> None:
266+
os.makedirs(FIXTURES, exist_ok=True)
267+
write_minimal()
268+
write_all_tool_types()
269+
write_nested_tools()
270+
write_unknown_fields()
271+
write_malformed_lines()
272+
print("Wrote 5 fixtures to", FIXTURES)
273+
274+
275+
if __name__ == "__main__":
276+
main()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{"type": "user", "timestamp": "2026-05-26T10:00:00Z", "cwd": "/sanitized/project/path", "message": {"content": [{"type": "text", "text": "Exercise all fifteen tool-result dispatch predicates"}]}, "sessionId": "session-sanitized-all-tool-types"}
2+
{"type": "assistant", "timestamp": "2026-05-26T10:01:00Z", "message": {"model": "claude-sanitized", "content": [{"type": "tool_use", "id": "tu-1", "name": "Bash", "input": {"command": "echo ok"}}], "usage": {"input_tokens": 10, "output_tokens": 5}}, "sessionId": "session-sanitized-all-tool-types"}
3+
{"type": "user", "timestamp": "2026-05-26T10:02:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"stdout": "ok\n", "stderr": "", "exitCode": 0}, "sessionId": "session-sanitized-all-tool-types"}
4+
{"type": "user", "timestamp": "2026-05-26T10:03:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"filePath": "/sanitized/project/path/a.py", "structuredPatch": "@@ sanitized"}, "sessionId": "session-sanitized-all-tool-types"}
5+
{"type": "user", "timestamp": "2026-05-26T10:04:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"filePath": "/sanitized/project/path/b.txt", "content": "sanitized body"}, "sessionId": "session-sanitized-all-tool-types"}
6+
{"type": "user", "timestamp": "2026-05-26T10:05:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"filenames": ["sanitized.py"], "numFiles": 1, "truncated": false}, "sessionId": "session-sanitized-all-tool-types"}
7+
{"type": "user", "timestamp": "2026-05-26T10:06:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"mode": "content", "numFiles": 1, "numLines": 2, "content": "sanitized match"}, "sessionId": "session-sanitized-all-tool-types"}
8+
{"type": "user", "timestamp": "2026-05-26T10:07:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"file": {"filePath": "/sanitized/project/path/readme.md", "numLines": 3, "content": "sanitized read"}}, "sessionId": "session-sanitized-all-tool-types"}
9+
{"type": "user", "timestamp": "2026-05-26T10:08:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"query": "sanitized query", "results": [{"url": "https://example.com/a"}]}, "sessionId": "session-sanitized-all-tool-types"}
10+
{"type": "user", "timestamp": "2026-05-26T10:09:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"url": "https://example.com/doc", "code": 200, "durationMs": 40}, "sessionId": "session-sanitized-all-tool-types"}
11+
{"type": "user", "timestamp": "2026-05-26T10:10:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"task_id": "task-sanitized-msg", "task_type": "sub"}, "sessionId": "session-sanitized-all-tool-types"}
12+
{"type": "user", "timestamp": "2026-05-26T10:11:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"retrieval_status": "found", "task": {"task_id": "task-sanitized-ret", "description": "REDACTED"}}, "sessionId": "session-sanitized-all-tool-types"}
13+
{"type": "user", "timestamp": "2026-05-26T10:12:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"agentId": "agent-sanitized-done", "totalDurationMs": 1000, "status": "completed"}, "sessionId": "session-sanitized-all-tool-types"}
14+
{"type": "user", "timestamp": "2026-05-26T10:13:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"agentId": "agent-sanitized-async", "isAsync": true, "status": "running", "description": "REDACTED background task"}, "sessionId": "session-sanitized-all-tool-types"}
15+
{"type": "user", "timestamp": "2026-05-26T10:14:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"newTodos": [{"id": "1", "content": "sanitized todo"}]}, "sessionId": "session-sanitized-all-tool-types"}
16+
{"type": "user", "timestamp": "2026-05-26T10:15:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"questions": [{"id": "q1"}], "answers": {"q1": "sanitized answer"}}, "sessionId": "session-sanitized-all-tool-types"}
17+
{"type": "user", "timestamp": "2026-05-26T10:16:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"plan": [], "filePath": "/sanitized/project/path/plan.md"}, "sessionId": "session-sanitized-all-tool-types"}
18+
{"type": "user", "timestamp": "2026-05-26T10:17:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"agentId": "agent-sanitized-overlap", "totalDurationMs": 500, "status": "completed", "message": "status update sanitized"}, "sessionId": "session-sanitized-all-tool-types"}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{"type": "user", "timestamp": "2026-05-26T10:00:00Z", "cwd": "/sanitized/project/path", "message": {"content": [{"type": "text", "text": "Valid line before malformed section"}]}, "sessionId": "session-sanitized-malformed-lines"}
2+
{"type": "assistant", "timestamp": "2026-05-26T10:01:00Z", "message": {"model": "claude-sanitized", "content": [{"type": "text", "text": "Recovered after bad lines."}], "usage": {"input_tokens": 10, "output_tokens": 5}}, "sessionId": "session-sanitized-malformed-lines"}
3+
4+
# parse_session skips lines where json.loads raises JSONDecodeError (blank, garbage, truncated JSON below).
5+
{not valid json
6+
{"type": "user", "timestamp": "2026-05-26T10:02:00Z", "message": {"content":
7+
{"type": "user", "timestamp": "2026-05-26T10:03:00Z", "cwd": "/sanitized/project/path", "message": {"content": [{"type": "text", "text": "Valid line after malformed section"}]}, "toolUseResult": {"stderr": "warn only", "exitCode": 1}, "sessionId": "session-sanitized-malformed-lines"}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"type": "user", "timestamp": "2026-05-26T10:00:00Z", "cwd": "/sanitized/project/path", "message": {"content": [{"type": "text", "text": "Sanitized minimal real-shaped session opener"}]}, "sessionId": "session-sanitized-minimal"}
2+
{"type": "assistant", "timestamp": "2026-05-26T10:01:00Z", "message": {"model": "claude-sanitized", "content": [{"type": "text", "text": "Acknowledged."}], "usage": {"input_tokens": 10, "output_tokens": 5}}, "sessionId": "session-sanitized-minimal"}
3+
{"type": "user", "timestamp": "2026-05-26T10:02:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"stdout": "sanitized output\n", "stderr": "", "exitCode": 0}, "sessionId": "session-sanitized-minimal"}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{"type": "user", "timestamp": "2026-05-26T10:00:00Z", "cwd": "/sanitized/project/path", "message": {"content": [{"type": "text", "text": "Parent turn with nested subagent tool activity"}]}, "sessionId": "session-sanitized-nested-tools"}
2+
{"type": "assistant", "timestamp": "2026-05-26T10:01:00Z", "message": {"model": "claude-sanitized", "content": [{"type": "tool_use", "id": "parent-tool-1", "name": "Task", "input": {"description": "sanitized subagent task"}}], "usage": {"input_tokens": 10, "output_tokens": 5}}, "sessionId": "session-sanitized-nested-tools"}
3+
{"type": "user", "timestamp": "2026-05-26T10:02:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"stdout": "sidechain output\n", "stderr": "", "exitCode": 0}, "isSidechain": true, "sessionId": "session-sanitized-nested-tools"}
4+
{"type": "progress", "timestamp": "2026-05-26T10:03:00Z", "toolUseID": "child-tool-1", "parentToolUseID": "parent-tool-1", "isSidechain": true, "data": {"type": "bash_progress", "output": "sanitized streaming chunk"}, "sessionId": "session-sanitized-nested-tools"}
5+
{"type": "assistant", "timestamp": "2026-05-26T10:04:00Z", "message": {"model": "claude-sanitized", "content": [{"type": "tool_use", "id": "nested-read-1", "name": "Read", "input": {"file_path": "/sanitized/project/path/nested.txt"}}], "usage": {"input_tokens": 10, "output_tokens": 5}}, "sessionId": "session-sanitized-nested-tools"}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"type": "user", "timestamp": "2026-05-26T10:00:00Z", "cwd": "/sanitized/project/path", "message": {"content": [{"type": "text", "text": "Forward-compat entry with unknown top-level keys"}]}, "_futureSchemaVersion": 2, "experimentalFlag": true, "sessionId": "session-sanitized-unknown-fields"}
2+
{"type": "assistant", "timestamp": "2026-05-26T10:01:00Z", "message": {"model": "claude-sanitized", "content": [{"type": "text", "text": "Handled unknown fields."}], "usage": {"input_tokens": 10, "output_tokens": 5}}, "sessionId": "session-sanitized-unknown-fields"}
3+
{"type": "user", "timestamp": "2026-05-26T10:02:00Z", "cwd": "/sanitized/project/path", "message": {"content": []}, "toolUseResult": {"stdout": "still bash\n", "stderr": "", "exitCode": 0, "_unknownToolMeta": {"vendor": "sanitized"}}, "sessionId": "session-sanitized-unknown-fields"}

0 commit comments

Comments
 (0)