Hmbown/CodeWhale · error
sub-agent transcript artifact is empty
Error message
sub-agent transcript artifact is empty
What it means
`load_subagent_transcript_artifact` read the transcript file but `raw.lines()` produced no first line — the artifact is empty. The loader requires at minimum a header line (kind=subagent_transcript_header), so a zero-byte file cannot be a valid transcript. This almost always means the file was truncated or created by something other than the persister.
Source
Thrown at crates/tui/src/tools/subagent/mod.rs:7588
&subagent_transcript_artifact_relative_path(agent_id),
)
}
/// Read the complete structured worker chat for the TUI Open action. The path
/// is derived from `agent_id` rather than accepted from handle JSON, so a
/// corrupted or model-supplied payload cannot redirect the reader outside the
/// manager state root.
pub fn load_subagent_transcript_artifact(
state_root: &Path,
agent_id: &str,
) -> Result<Vec<Message>> {
let state_root = normalize_subagent_workspace(state_root);
let path = checked_subagent_transcript_artifact_path(&state_root, agent_id)?;
let raw = read_subagent_state_file(&state_root, &path)?;
let mut lines = raw.lines();
let header_line = lines
.next()
.ok_or_else(|| anyhow!("sub-agent transcript artifact is empty"))?;
let header: Value = serde_json::from_str(header_line)?;
if header.get("kind").and_then(Value::as_str) != Some("subagent_transcript_header")
|| header.get("schema_version").and_then(Value::as_u64)
!= Some(u64::from(SUBAGENT_TRANSCRIPT_ARTIFACT_SCHEMA_VERSION))
|| header.get("agent_id").and_then(Value::as_str) != Some(agent_id)
{
return Err(anyhow!(
"sub-agent transcript artifact header does not match agent {agent_id}"
));
}
let mut messages = Vec::new();
for line in lines.filter(|line| !line.trim().is_empty()) {
let record: Value = serde_json::from_str(line)?;
if record.get("kind").and_then(Value::as_str) != Some("message") {
return Err(anyhow!("unknown sub-agent transcript artifact record"));
}
let index = recordView on GitHub (pinned to 0c42157ee5)
Solutions
- Check the artifact's size under the sub-agent state root; restore from backup, or delete it so the manager recreates it on the next spawn.
- Re-run the sub-agent task — transcripts are per-agent artifacts and regenerate on rerun.
- Keep external processes from writing the state dir; all writes should go through the persister's path.
Defensive patterns
Strategy: validation
Validate before calling
let path = checked_subagent_transcript_artifact_path(&state_root, agent_id)?;
let meta = std::fs::symlink_metadata(&path)?;
if meta.len() == 0 { /* empty artifact: regenerate instead of loading */ } Type guard
fn artifact_is_loadable(raw: &str) -> bool {
raw.lines().next().is_some()
} Try / catch
match load_subagent_transcript_artifact(&state_root, agent_id) {
Err(e) if e.to_string().contains("artifact is empty") => { /* delete + respawn */ }
r => r?,
} Prevention
- Write artifacts atomically; never truncate-then-write.
- Keep external processes out of the sub-agent state root.
- Check state-dir disk space on long-running hosts.
When it happens
Trigger: Artifact truncated to zero bytes by a crashed/interrupted write, external tooling overwriting the state file, a full disk, or a test fixture that created an empty file at the expected path.
Common situations: Hard kill during child spawn; state dir on a full or flaky disk; backup/sync/editor software touching state files; manually crafted fixtures.
Related errors
- unknown sub-agent transcript artifact record
- sub-agent transcript history shrank from {} to {} messages
- sub-agent transcript artifact header does not match agent {a
- sub-agent transcript message is missing its index
- sub-agent transcript message index {index} does not follow {
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/d56276e5b18301a8.
Report an issue: GitHub.