[BUG] Closed session transcripts are bulk-rewritten with a shared mtime, destroying file-date signal (114 files at one identical second)

Status Open
Reported on v2.1.247
Maintainer reply None cached
Activity 0 comments · opened Aug 31, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version available to me (2.1.247, bundled with Claude Desktop; latest published is 2.1.251)

What's Wrong?

In ~/.claude/projects/<project>/, a large batch of session transcripts share one identical filesystem mtime — the same single second — while their contents show no activity anywhere near that time. The result is that mtime no longer dates a session.

Measured on this machine on 2026-08-31:

  • 1,206 top-level session .jsonl files across all project dirs; 1,090 distinct mtime epochs
  • largest cluster: 114 files all at epoch 1787422837 (2026-08-22T21:20:37 local), all within a single project dir
  • for those 114, mtime minus the last timestamped record inside the file: min 3 days, median 17 days, max 47 days — 93 of 114 are stamped more than 7 days after their last real event
  • tail record type of those files: last-prompt ×76, mode ×21, attachment ×10, queue-operation ×4, user ×2, assistant ×1

The last-prompt and mode records carry no timestamp field. So the write leaves no trace in content — only mtime moves. Two examples: a session whose last real event is 2026-07-22T14:00:18Z, and one whose last real event is 2026-08-04T10:19:24Z, both end with an untimed last-prompt record and both carry the 2026-08-22 mtime.

I have not identified what triggers the write, and I'm deliberately not asserting a cause. The observable facts are: (1) many long-closed transcripts were appended to in one bulk operation, (2) the appended records are timestamp-free metadata types, (3) mtime moved and content did not.

What Should Happen?

Either of these would fix it:

  • writes to an already-closed transcript preserve its mtime, or
  • any appended record carries a timestamp, so the write is auditable from content

Currently neither holds: the file's date says one thing, its contents say another, and nothing records what caused the write.

Why It Matters

mtime is the cheapest and most common way to date a session. Concretely:

  • ls -lt ~/.claude/projects/<project>/ reports a burst of 114 sessions on a day when no work happened, and hides the days work actually did happen
  • any tooling that selects "recent sessions" by mtime — backup selection, log rotation, cleanup scripts, session-resume pickers, usage analysis — silently operates on the wrong set
  • a retention policy keyed on mtime will preserve stale sessions and can age out recent ones

This is a silent-wrong-output failure: nothing errors, and the wrong answer looks exactly like the right one.

Steps to Reproduce

Run against a ~/.claude/projects tree with a few months of history:

import os, json, datetime, collections

root = os.path.expanduser("~/.claude/projects")
files = []
for dp, dn, fn in os.walk(root):
    if "/subagents" in dp:
        continue
    files += [os.path.join(dp, f) for f in fn if f.endswith(".jsonl")]

c = collections.Counter(int(os.stat(f).st_mtime) for f in files)
epoch, n = c.most_common(1)[0]
print(f"{n} files share mtime {epoch} "
      f"({datetime.datetime.fromtimestamp(epoch)}) out of {len(files)}")

for p in files:
    if int(os.stat(p).st_mtime) != epoch:
        continue
    last = None
    tail = None
    with open(p, errors="replace") as fh:
        for ln in fh:
            try:
                o = json.loads(ln)
            except Exception:
                continue
            if o.get("timestamp"):
                last = o["timestamp"]
            tail = o.get("type")
    print(os.path.basename(p), "last_event:", last, "tail_type:", tail)

Expected if healthy: mtimes spread across the days work occurred, and each file's mtime close to its last timestamped record.

Is this a regression?

Unknown — I have no known-good version to compare against.

Environment

  • macOS (Darwin 25.5.0), Apple Silicon
  • CLAUDE_CODE_ENTRYPOINT=claude-desktop, session running 2.1.247

View original on GitHub ↗