Skip to content

cmd -p turns without tool calls produce empty session transcript files #836

Description

@powerfulparadox

Summary

In headless mode, a run that performs no tool calls leaves a 0-byte session transcript.
Because --resume <id> reads that transcript, the next run silently starts with no memory of
the previous one. There is no error or warning — the model simply reports that it has no
record of the conversation.

Runs that make at least one tool call (executed, errored, or even permission-blocked)
persist the transcript normally and resume correctly.

This makes the external session orchestration tool I'm trying to hack together much more difficult to implement.

Expected Behavior

Attempting a turn on a session of a known ID with cmd -p should produce some kind of session-resume semantics, with the model being able to read the transcript for information from earlier turns. This makes answering questions about things covered earlier in the session possible. It's also documented that every completed turn is committed to the transcript as it commits, so if I have a session that has been assigned a session ID and I reference it from cmd -p I expect a transcript the new turn can consult.

Actual Behavior

cmd -p session turns with tool calls produce transcript files as expected. cmd -p session turns without tool calls seem to produce 0B, empty transcript files.

Steps to reproduce the issue

mkdir -p /tmp/repro && cd /tmp/repro
cmd -p 'Reply with just: OK. Do not use any tools.' \
  --output-format json --permission-mode plan \
  --trust --skip-onboarding --no-auto-update   # note the sessionId

# The transcript should appear here, and is 0 bytes:
ls -la ~/.commandcode/projects/tmp-repro/<sessionId>.jsonl

# Resume and ask about the first turn:
cmd -p 'What did I ask you first?' \
  --output-format json --resume <sessionId> \
  --permission-mode plan --trust --skip-onboarding --no-auto-update

Repeat the same steps with a prompt that requires a tool call (e.g. "Read target.ts, then
reply with the number"
), and the transcript is written and resume behaves correctly.

Full agent investigation attached.

cmd-headless-transcript-finding.md

Command Code Version

1.53.0

Operating System

Linux

Terminal/IDE

Unknown

Shell

fish

Session file (optional)

Just going to note here that Github rejects .jsonl as an unsupported file format. That seems to create an impasse with the instructions to upload a jsonl file here, but maybe I'm missing something.

Fix prompt (optional)

Bug: in headless mode (`cmd -p`), a run that makes NO tool calls leaves a 0-byte session
transcript at ~/.commandcode/projects/<slug>/<sessionId>.jsonl. Because `--resume <id>` reads
that file, the next run starts with no context and reports no error. Runs that make at least
one tool call persist correctly. Reported on 1.53.0 (Linux, Node 26). Full evidence:
docs/cmd-headless-transcript-finding.md

Step 1 — reproduce and characterise, before reading any code. For each case below, run the
command, then report the transcript's byte size and whether a follow-up `--resume` remembers a
fact from the first turn:

  a. no tool call:            "Reply with just: OK. Do not use any tools."
  b. read tool:               "Read target.ts, then reply with just the number."
  c. write blocked by plan:   "Create note.txt containing hello." (plan mode)
  d. write allowed:           same prompt, auto-accept mode
  e. tool that errors:        "Read /nonexistent/file.ts and report what happened."
  f. no tools but slow/long:  "Write a 400-word essay about bicycles. No tools."

Use: cmd -p "<prompt>" --output-format json --permission-mode <mode> --trust --skip-onboarding
--no-auto-update, with the prompt's working directory as cwd. Write stdout OUTSIDE the working
directory, or the model can read the prompt back out of the redirect file and fake a
successful resume.

Report a table of (case, tool-call count, transcript bytes, resumed yes/no). State which
factor actually determines persistence.

Step 2 — find the cause. Locate where print mode commits a completed turn to the transcript
and compare it against what happens between an assistant message with no tool_use blocks and
one with tool_use blocks. Check whether the save is gated on something that only holds when a
tool call occurred (a turn counter, a tool-result message being appended, a state transition,
or a write path that only runs when the loop continues rather than ending). Note whether the
checkpoint sidecar is written on a separate path — it IS written even when the transcript is
empty, which should narrow this down.

Step 3 — fix so a completed turn is committed regardless of whether it contained tool calls,
consistent with the documented guarantee that "every completed turn is appended to the
transcript as it commits, not on exit". Keep empty sessions out of `/resume` as today.

Step 4 — regression test. Add a headless test that makes a no-tool call, completes the turn,
and asserts the transcript exists and is non-empty, then that resuming by id sees the earlier
turn. It must fail before the fix.

Step 5 — separately, make the failure loud: when `--resume <id>` resolves to a missing or
empty transcript, warn on stderr (and in --output-format json) instead of silently continuing
with no context.

Report: the characterisation table, the code path responsible, the fix, and the test result.

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions