@@ -237,6 +237,23 @@ emit_bg_shell_launch_result() {
237237 }'
238238}
239239
240+ emit_bg_shell_launch_result_with_output_path () {
241+ local tool_use_id=" $1 " bg_task_id=" $2 " output_path=" $3 "
242+ jq -c -n \
243+ --arg id " $tool_use_id " \
244+ --arg bid " $bg_task_id " \
245+ --arg out " $output_path " \
246+ ' {
247+ type:"user",
248+ message:{
249+ role:"user",
250+ content:[{tool_use_id:$id, type:"tool_result",
251+ content:[{type:"text", text:("Command running in background with ID: " + $bid + ". Output is being written to: " + $out + ". You will be notified when it completes.")}]}]
252+ },
253+ toolUseResult:{backgroundTaskId:$bid}
254+ }'
255+ }
256+
240257emit_task_completion_event () {
241258 local task_id=" $1 " tool_use_id=" $2 " status=" ${3:- completed} "
242259 local notif
@@ -1458,5 +1475,72 @@ run_stop_hook_with_input "$AC24_REPO" "$AC24_INPUT" "" "$TEST_DIR/bin/lsof-dead"
14581475rm -rf " /tmp/claude-${AC24_UID} /${AC24_SLUG} /ac24" 2> /dev/null || true
14591476assert_reached_codex " AC-24: dead/orphaned task (lsof no holder) is pruned; Codex review runs"
14601477
1478+ # ---------------- AC-25 ----------------
1479+ # Session resume regression: when Claude resumes a session, the current
1480+ # transcript file has a NEW session id, but background tasks launched
1481+ # earlier physically wrote their .output files under the OLD session
1482+ # directory. The transcript launch message records the real path. The
1483+ # liveness probe must look at that real path, not at a path derived
1484+ # from the current transcript's session id, or orphaned dead tasks are
1485+ # never pruned.
1486+ echo " Test AC-25: liveness probe follows real output path from transcript on session resume"
1487+ AC25_REPO=" $TEST_DIR /ac25"
1488+ create_full_fixture " $AC25_REPO " > /dev/null
1489+ AC25_UID=$( id -u)
1490+ AC25_SLUG=$( basename " $TRANSCRIPTS_DIR " )
1491+ AC25_OLD_SESSION=" aaaaaaaa-1111-2222-3333-444444444444"
1492+ AC25_NEW_SESSION=" bbbbbbbb-5555-6666-7777-888888888888"
1493+ AC25_TASK_ID=" shell_resumed_session"
1494+ AC25_REAL_OUTPUT=" /tmp/claude-${AC25_UID} /${AC25_SLUG} /${AC25_OLD_SESSION} /tasks/${AC25_TASK_ID} .output"
1495+
1496+ # Build the launch event with the real (old-session) output path embedded
1497+ # in the Claude Code launch message.
1498+ AC25_LAUNCH=$( emit_tool_use_assistant " toolu_AC25" " Bash" ' ,"command":"sleep 30"' )
1499+ AC25_RESULT=$( emit_bg_shell_launch_result_with_output_path " toolu_AC25" " $AC25_TASK_ID " " $AC25_REAL_OUTPUT " )
1500+
1501+ # Write the transcript under the NEW session id (resume session).
1502+ AC25_TRANSCRIPT=" /tmp/claude-${AC25_UID} /${AC25_SLUG} /${AC25_NEW_SESSION} .jsonl"
1503+ write_transcript " $AC25_TRANSCRIPT " " $AC25_LAUNCH " " $AC25_RESULT "
1504+
1505+ # The real output file lives in the OLD session directory.
1506+ mkdir -p " $( dirname " $AC25_REAL_OUTPUT " ) "
1507+ touch " $AC25_REAL_OUTPUT "
1508+
1509+ AC25_INPUT=$( jq -c -n --arg tp " $AC25_TRANSCRIPT " ' {transcript_path:$tp}' )
1510+ run_stop_hook_with_input " $AC25_REPO " " $AC25_INPUT " " " " $TEST_DIR /bin/lsof-dead"
1511+ rm -rf " /tmp/claude-${AC25_UID} /${AC25_SLUG} /${AC25_OLD_SESSION} " \
1512+ " /tmp/claude-${AC25_UID} /${AC25_SLUG} /${AC25_NEW_SESSION} .jsonl" 2> /dev/null || true
1513+ assert_reached_codex " AC-25: dead task pruned using real output path from transcript, not derived new-session path"
1514+
1515+ # ---------------- AC-25b ----------------
1516+ # Same as AC-25, but the recorded output path contains a space. The
1517+ # regex used to extract the path must not stop at the first whitespace
1518+ # token, or it will fall back to the derived new-session path and the
1519+ # dead task will never be pruned.
1520+ echo " Test AC-25b: liveness probe handles whitespace in recorded output path"
1521+ AC25B_REPO=" $TEST_DIR /ac25b"
1522+ create_full_fixture " $AC25B_REPO " > /dev/null
1523+ AC25B_UID=$( id -u)
1524+ AC25B_SLUG=$( basename " $TRANSCRIPTS_DIR " )
1525+ AC25B_OLD_SESSION=" aaaaaaaa-1111-2222-3333-444444444444"
1526+ AC25B_NEW_SESSION=" bbbbbbbb-5555-6666-7777-888888888888"
1527+ AC25B_TASK_ID=" shell_resumed_session_space"
1528+ AC25B_REAL_OUTPUT=" /tmp/claude-${AC25B_UID} /${AC25B_SLUG} /${AC25B_OLD_SESSION} /tasks/with space/${AC25B_TASK_ID} .output"
1529+
1530+ AC25B_LAUNCH=$( emit_tool_use_assistant " toolu_AC25B" " Bash" ' ,"command":"sleep 30"' )
1531+ AC25B_RESULT=$( emit_bg_shell_launch_result_with_output_path " toolu_AC25B" " $AC25B_TASK_ID " " $AC25B_REAL_OUTPUT " )
1532+
1533+ AC25B_TRANSCRIPT=" /tmp/claude-${AC25B_UID} /${AC25B_SLUG} /${AC25B_NEW_SESSION} .jsonl"
1534+ write_transcript " $AC25B_TRANSCRIPT " " $AC25B_LAUNCH " " $AC25B_RESULT "
1535+
1536+ mkdir -p " $( dirname " $AC25B_REAL_OUTPUT " ) "
1537+ touch " $AC25B_REAL_OUTPUT "
1538+
1539+ AC25B_INPUT=$( jq -c -n --arg tp " $AC25B_TRANSCRIPT " ' {transcript_path:$tp}' )
1540+ run_stop_hook_with_input " $AC25B_REPO " " $AC25B_INPUT " " " " $TEST_DIR /bin/lsof-dead"
1541+ rm -rf " /tmp/claude-${AC25B_UID} /${AC25B_SLUG} /${AC25B_OLD_SESSION} " \
1542+ " /tmp/claude-${AC25B_UID} /${AC25B_SLUG} /${AC25B_NEW_SESSION} .jsonl" 2> /dev/null || true
1543+ assert_reached_codex " AC-25b: dead task pruned when real output path contains whitespace"
1544+
14611545print_test_summary " Stop Hook Background-Task Allow Test Summary"
14621546exit $?
0 commit comments