@@ -548,6 +548,30 @@ describe("conversationTurnsToJsonlEntries", () => {
548548 expect ( conv [ 2 ] . parentUuid ) . toBe ( conv [ 1 ] . uuid ) ;
549549 } ) ;
550550
551+ it . each ( [ undefined , null ] ) (
552+ "emits input: {} for tool calls whose input is %s" ,
553+ ( missingInput ) => {
554+ const lines = conversationTurnsToJsonlEntries (
555+ [
556+ {
557+ role : "assistant" ,
558+ content : [ ] ,
559+ toolCalls : [
560+ { toolCallId : "tc-1" , toolName : "Bash" , input : missingInput } ,
561+ ] ,
562+ } ,
563+ ] ,
564+ config ,
565+ ) ;
566+
567+ const conv = parseConversationEntries ( lines ) ;
568+ expect ( conv ) . toHaveLength ( 1 ) ;
569+ expect ( conv [ 0 ] . message . content ) . toEqual ( [
570+ { type : "tool_use" , id : "tc-1" , name : "Bash" , input : { } } ,
571+ ] ) ;
572+ } ,
573+ ) ;
574+
551575 it ( "sets stop_reason only on last block, null on intermediate" , ( ) => {
552576 const lines = conversationTurnsToJsonlEntries (
553577 [
@@ -1132,6 +1156,29 @@ describe("end-to-end: S3 log entries -> JSONL output", () => {
11321156 expect ( msg1 . id ) . toBe ( msg2 . id ) ;
11331157 expect ( msg2 . id ) . toBe ( msg3 . id ) ;
11341158 } ) ;
1159+
1160+ it ( "emits input: {} when the tool input never reached the logs" , ( ) => {
1161+ const s3Logs : StoredEntry [ ] = [
1162+ s3Entry ( "user_message" , {
1163+ content : { type : "text" , text : "run the tests" } ,
1164+ } ) ,
1165+ s3Entry ( "tool_call" , {
1166+ toolCallId : "tc-lost" ,
1167+ _meta : { claudeCode : { toolName : "Bash" } } ,
1168+ } ) ,
1169+ ] ;
1170+
1171+ const turns = rebuildConversation ( s3Logs ) ;
1172+ const lines = conversationTurnsToJsonlEntries ( turns , config ) ;
1173+ const conv = filterConv ( lines . map ( ( l ) => JSON . parse ( l ) ) ) ;
1174+
1175+ const toolUseLine = conv . find ( ( e ) => e . type === "assistant" ) ;
1176+ expect ( toolUseLine ) . toBeDefined ( ) ;
1177+ const content = ( toolUseLine ?. message as { content : unknown [ ] } ) . content ;
1178+ expect ( content ) . toEqual ( [
1179+ { type : "tool_use" , id : "tc-lost" , name : "Bash" , input : { } } ,
1180+ ] ) ;
1181+ } ) ;
11351182} ) ;
11361183
11371184describe ( "sanitizeSessionJsonl" , ( ) => {
@@ -1242,6 +1289,32 @@ describe("sanitizeSessionJsonl", () => {
12421289 ] ) ;
12431290 } ) ;
12441291
1292+ it . each ( [
1293+ [ "a missing" , { type : "tool_use" , id : "tc-1" , name : "Bash" } ] ,
1294+ [ "a null" , { type : "tool_use" , id : "tc-1" , name : "Bash" , input : null } ] ,
1295+ ] ) ( "adds input: {} to tool_use blocks with %s input" , async ( _ , block ) => {
1296+ const file = await writeJsonl ( [
1297+ {
1298+ type : "assistant" ,
1299+ uuid : "a1" ,
1300+ parentUuid : null ,
1301+ message : {
1302+ role : "assistant" ,
1303+ content : [ { type : "text" , text : "running" } , block ] ,
1304+ } ,
1305+ } ,
1306+ ] ) ;
1307+
1308+ expect ( await sanitizeSessionJsonl ( file ) ) . toBe ( true ) ;
1309+
1310+ const lines = await readJsonl ( file ) ;
1311+ const assistant = lines [ 0 ] . message as { content : unknown } ;
1312+ expect ( assistant . content ) . toEqual ( [
1313+ { type : "text" , text : "running" } ,
1314+ { type : "tool_use" , id : "tc-1" , name : "Bash" , input : { } } ,
1315+ ] ) ;
1316+ } ) ;
1317+
12451318 it ( "sanitizes empty blocks in user lines too" , async ( ) => {
12461319 const file = await writeJsonl ( [
12471320 {
0 commit comments