Repository navigation
fix(state): tolerate concurrent creation of state directories - #35
Conversation
Two CLI processes starting on a fresh home both saw ~/.context-tree (or repositories/, snapshots/) as absent and raced to mkdir it; the loser failed its sync with EEXIST. Creation now treats EEXIST as success and still validates the winning entry, so symlinks and non-directories are rejected as before. The Skill install directory helper gets the same fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yuezengwu
left a comment
There was a problem hiding this comment.
Independently reviewed exact head 6dad8bfd01a291c0237156378ec74757ac600baa against actual main a089f6996b7435d7f3eb8d1f5fb8e54fe62e8c72. The mkdir race fix tolerates only EEXIST and then validates the winning entry with the existing real-directory/symlink checks on both state and Skill-directory paths. Other filesystem errors still propagate. No schema, lock-timeout, or credential contract changes.
Frozen install (host-install scripts disabled), repository check, typecheck,130 tests across15 files, and real-tarball check:package pass. The new deterministic test covers a concurrent real-directory winner and rejects a symlink winner; this is controlled local validation, not a live multi-process stress or release. No blocker found. Version/tag/npm publication remain separate.
Problem
privateDirectorychecks whether a state directory exists and then callsmkdirSyncwithoutrecursive. When two CLI processes start on a fresh home at the same time, both see~/.context-tree(orrepositories/,snapshots/) as absent, and the secondmkdirfails withEEXIST. That source's sync then fails withCONTEXT_TREE_FAILED.OpenTag hits this now that its command gateway syncs each selected source in its own concurrent CLI child (first-tree-ai/opentag#1287). On a fresh home with two sources from one repository, one source fails with:
The lock wait from #30 handles a warm home; this race happens before any lock exists.
Change
mkdirUnlessExiststreatsEEXISTas success.privateDirectorythen validates the entry that won the race with the samerealDirectoryWithoutSymlinkscheck, so a symlink or a non-directory is still rejected.ensureRealDirectory) gets the same fix and re-runs its existing symlink and directory check.Tests
New
tests/state-directory-race.test.tsmakeslstatSyncreport a path as absent once, as if another process created it right after the check:EEXIST);EEXIST).pnpm check,pnpm typecheck, andpnpm test(15 files, 130 tests) pass.Release PR #34 (1.1.1) is open. It would be good to land this first so the release includes it; OpenTag #1287 will pin the release that does.
🤖 Generated with Claude Code