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 = record

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. 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.
  2. Re-run the sub-agent task — transcripts are per-agent artifacts and regenerate on rerun.
  3. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/d56276e5b18301a8. Report an issue: GitHub.