Skip to content

Skip iCloud-evicted source files instead of failing - #3

Merged
PEEKPerformer merged 3 commits into
mainfrom
fix/icloud-evicted-sources
Sep 17, 2026
Merged

PEEKPerformer merged 3 commits into
mainfrom
fix/icloud-evicted-sources

Conversation

@PEEKPerformer

Copy link
Copy Markdown
Owner

Stacked on #2 (which is stacked on #1). This PR's diff is only its own commit.

What this PR does

When a source transcript has been evicted to iCloud, ingest reads a mirrored copy that is still on disk. If there isn't one, it skips the session as skipped-offline instead of logging a worker failure.

Why

Every sweep logged dozens of holotype: worker failed on ...: OSError: [Errno 11] Resource deadlock avoided, over 14,000 lines in the error log so far.

The preferred Claude Code source was a backup mirror under ~/Documents, which iCloud syncs. With "Optimize Mac Storage" on, iCloud had evicted 3,556 of its 11,311 transcripts. Only a placeholder (SF_DATALESS) stays on disk. The launchd tick runs in the background and isn't allowed to download the file, so reading it fails with EDEADLK.

  • Dedup: still keeps the first path listed for a session, unless that copy is evicted and a later path has the file on disk. On the real archive, every evicted candidate had a local copy in ~/.claude/projects (0 evicted candidates remain after this change).
  • Deposit: checks SF_DATALESS before reading and also catches EDEADLK, returning skipped-offline. The prior deposit stands, and the session is read again once the file is local.
  • Tick summary: shows offline=N when any sessions were skipped.

Type of change

  • Bug fix (non-breaking change that fixes a documented or observed defect)
  • New feature (non-breaking change that adds capability)
  • Breaking change (alters existing behavior; requires major-version bump and CHANGELOG entry)
  • Documentation only
  • Performance improvement (no functional change)
  • Internal refactor (no user-visible change)
  • CI / tooling

Non-negotiables check

  • Forensic completeness (no filtering of transcript content)
  • Provenance via hash chain (SHA-256 per session, verifiable with stock Unix tools)
  • Git is the archive substrate
  • Environment capture (host CLI version, OS, holotype version, project-repo git state)
  • Append-only by design
  • No silent network behavior

Holotype still never triggers an iCloud download itself.

Selftest

  • python scripts/selftest.py passes locally
  • If this PR adds a feature or fixes a bug, a new selftest assertion exercises the change
  • CI is green (GitHub Actions workflow on this PR)

CI can't create an evicted file, so the new assertions patch is_dataless. They check that dedup picks the mirror's on-disk copy over an evicted first path, and that deposit_one returns skipped-offline for an evicted source.

Manifest / archive-format impact

  • No archive-format change
  • manifest_version bumped from N to N+1
  • index_schema_version bumped from N to N+1
  • On-disk archive layout changed (describe migration path)

Documentation

  • CHANGELOG.md updated (under the next-release section, 2.4.1)
  • README.md updated if user-facing behavior changed
  • SKILL.md updated if wizard flow changed
  • docs/ updated as appropriate

Cross-platform impact

  • Verified on macOS
  • Verified on Linux (CI; st_flags is absent there, so is_dataless is always false)
  • Verified on Windows (not applicable: no Windows runner configured)
  • CI passes on all configured runners

AI assistance

Diagnosis, fix, and draft written by Claude Code, from the error log, ls -lO on the failing sources, and a discovery run against the real config. Selftest run locally.

Anything else reviewers should know

Out of scope: a session whose only copy is evicted and that was never deposited stays unarchived until the file is local again. It is counted in offline=N but not named. No such session exists on the real archive today.

git runs `git maintenance run --auto --detach` after every commit.
Once the loose-object count passes gc.auto, each commit starts its own
detached full repack, and nothing stops a second one from starting
while the first is still running. An archive's pack is mostly zstd or
git-crypt blobs, so each repack holds gigabytes. One ingest tick makes
one commit per changed session, which stacked repacks until a 16 GB
Mac with a 7.3 GiB archive ran out of memory and had to be
force-restarted.

Set maintenance.auto=false and gc.auto=0 in the archive's local git
config. init.py sets them after `git init`. ingest.py sets them under
the ingest lock before the first commit, which migrates existing
archives on their next tick. Loose objects now wait for a manual
`git repack -d`.

The selftest checks both the init path and the migration path.
On a real archive 1,832 compressed deposits failed the sweep fsck,
yet every one decompressed to its canonical sha256. The v2.3.0 pure
migration copied sha256_compressed from the prior manifest instead of
hashing the file, so stale values survived, and the fsck compares only
the compressed hash. About 40 false failures per sweep slice buried
any real one.

Hash the file during a pure migration. In the fsck, fall back to the
canonical hash when the compressed one disagrees and count a match as
stale, not failed. Add scripts/repair_compressed_hashes.py to rewrite
the stale values in one commit, touching only that field and only
after the canonical hash matches.
The preferred Claude Code source on the maintainer's machine is a
backup mirror in ~/Documents, which iCloud syncs. iCloud had evicted
3,556 of its 11,311 transcripts to the cloud. The launchd tick may not
download an evicted file, so every read failed with EDEADLK and each
sweep logged dozens of "Resource deadlock avoided" worker failures.

Dedup now prefers a later path whose copy is on disk over an evicted
first path. A deposit whose source is still evicted returns
skipped-offline and leaves the prior deposit in place.
@PEEKPerformer
PEEKPerformer changed the base branch from fix/stale-compressed-hash to main September 17, 2026 12:42
@PEEKPerformer
PEEKPerformer merged commit 0d5dcdb into main Sep 17, 2026
@PEEKPerformer
PEEKPerformer deleted the fix/icloud-evicted-sources branch September 17, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant