Skip to content

session-end-archive: a corrupt archive is never repaired — gzip -l failure exits 0 through the ERR trap #439

Description

@evanharmon1

Invariant

The hook is best-effort by design: every failure path exits 0 so session exit is
never noisy. "Best effort" must still mean it retries — a transient or
corrupt state must not become permanent.

Current violation (observed 2026-08-12)

A corrupt archive is never repaired.

existing="$(find "$archive_dir" -maxdepth 1 -name "*-${session_id}.jsonl.gz" | head -1)"
if [[ -n "$existing" && ! "$transcript" -nt "$existing" ]]; then
    src_size="$(wc -c <"$transcript" | tr -d ' ')"
    arch_size="$(gzip -l "$existing" | awk 'NR==2{print $2}')"
    [[ "$src_size" != "$arch_size" ]] || exit 0
fi

If $existing is truncated or corrupted and is not older than the transcript,
gzip -l exits nonzero. The global trap 'exit 0' ERR then fires and the
script exits successfully, without rewriting the archive. Every later
SessionEnd for that session takes the same path, so the corrupt file survives
forever and the freshness check can never replace it.

Compounding it: exec 2>/dev/null means nothing is reported, and the archive
is the only copy once Claude Code's cleanupPeriodDays deletes the transcript.

Rated P2 rather than higher: writes are atomic (temp file + mv), so a corrupt
archive is unlikely to arise in the first place, and no new data is lost
beyond what the corruption already took.

Verify

tmp=$(mktemp -d); mkdir -p "$tmp/proj"
printf '{"a":1}\n' > "$tmp/proj/sess.jsonl"
printf 'not gzip' > "$tmp/archive/20260812-000000-proj-sess.jsonl.gz" 2>/dev/null \
  || { mkdir -p "$tmp/archive"; printf 'not gzip' > "$tmp/archive/20260812-000000-proj-sess.jsonl.gz"; }
touch "$tmp/archive/20260812-000000-proj-sess.jsonl.gz"   # archive not older than transcript
CLAUDE_TRANSCRIPT_ARCHIVE_DIR="$tmp/archive" \
  ./templates/claude-hooks/session-end-archive/session-end-archive.sh <<JSON
{"session_id":"sess","transcript_path":"$tmp/proj/sess.jsonl","cwd":"$tmp/proj"}
JSON
gzip -t "$tmp/archive"/*.gz && echo REPAIRED || echo "STILL CORRUPT (violation)"

Prints STILL CORRUPT while violated; REPAIRED once fixed.

Acceptance criteria

  • An unreadable or corrupt existing archive is treated as stale and replaced
  • The gzip -l failure does not reach the ERR trap as a success path
  • A regression test covers "existing archive is corrupt, transcript unchanged"
  • Behaviour is unchanged when the existing archive is valid and current

Found while wiring this hook into harmon-init's devcontainer
(evanharmon1/harmon-init#816), where the script is vendored verbatim.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions