Skip to content

fix: prevent corruption when agent files share a target - #723

Open
Davide Leopardi (Hexecu) wants to merge 1 commit into
langchain-ai:mainfrom
Hexecu:fix/code-mode-agent-file-aliases
Open

fix: prevent corruption when agent files share a target#723
Davide Leopardi (Hexecu) wants to merge 1 commit into
langchain-ai:mainfrom
Hexecu:fix/code-mode-agent-file-aliases

Conversation

@Hexecu

Copy link
Copy Markdown

Fixes #719.

Reported by Neils Christoffersen (@NeilsC) in #719.

Context

PR #547 made code-mode setup prepare both agent files before writing either one,
so malformed markers cannot partially refresh the other file. PR #640 then
intentionally gave AGENTS.md the full instructions and CLAUDE.md a short
pointer to them.

Those two guarantees conflict when both paths refer to the same physical file.
The setup prepared different contents from the same initial state, then raced
two writeFile calls against one target. Depending on filesystem timing, the
first run could silently leave a mixed managed block and a later run would
reject the duplicated markers.

What changed

  • Resolve managed paths to a physical file identity before preparing content.
    This follows relative symlinks and symlink chains, handles dangling symlinks,
    and uses device/inode identity for hard links.
  • Prepare and write each physical target once. AGENTS.md remains first in the
    existing ordered list, so a shared target receives the canonical full snippet
    instead of the CLAUDE.md pointer.
  • Reject symlink cycles without replacing either link.
  • Keep the existing prepare-all-before-write behavior for distinct files, and
    keep malformed shared targets byte-for-byte unchanged.

This does not change either generated snippet or the behavior of repositories
where AGENTS.md and CLAUDE.md are distinct files.

Regression coverage

The focused tests use real temporary files rather than mocking fs. They run
setup twice and cover:

  • CLAUDE.md -> AGENTS.md, including preservation of surrounding user content
    and the symlink itself;
  • a dangling CLAUDE.md -> AGENTS.md before AGENTS.md exists;
  • hard-linked agent files;
  • symlink cycles;
  • malformed content shared through a symlink;
  • the existing distinct-file, idempotence, marker-validation, and
    all-validate-before-write cases.

The primary symlink regression test fails against the current main baseline
(27d835c) on the second setup attempt because the first attempt leaves
malformed or duplicated markers. It passes after this change.

I also ran the compiled implementation against the local filesystem through 50
fresh repositories per topology, before and after the fix:

Topology main This change
Relative symlink to existing AGENTS.md 50/50 corrupted; 47 runs rejected 50/50 stable
Dangling symlink to missing AGENTS.md 47/50 corrupted; 44 runs rejected 50/50 stable
Reverse symlink (AGENTS.md -> CLAUDE.md) 50/50 corrupted; 47 runs rejected 50/50 stable
Hard-linked files 4/50 corrupted; 2 runs rejected 50/50 stable
Symlink chain 49/50 corrupted; 44 runs rejected 50/50 stable
Ten concurrent setup calls on one symlink target 49/50 corrupted; 48 runs rejected 50/50 stable
Distinct regular files 50/50 retained separate full/pointer snippets 50/50 unchanged
Malformed shared target 50/50 rejected and unchanged 50/50 rejected and unchanged

For every stable alias case, both paths had identical content, the link was
preserved, the full AGENTS.md snippet won, and exactly one start/end marker
pair remained.

Related work

Test results

  • pnpm exec vitest run test/ingestion/code-mode.test.ts on Node 22.20.0 — 29
    passed.
  • pnpm exec vitest run test/ingestion/code-mode.test.ts on Node 24.12.0 — 29
    passed.
  • pnpm test on Node 22.20.0 — 219 files passed, 2 skipped; 2,911 tests
    passed, 3 skipped.
  • pnpm test on Node 24.12.0 — 219 files passed, 2 skipped; 2,911 tests
    passed, 3 skipped.
  • pnpm run format:check — passed.
  • pnpm run lint:check — passed.
  • pnpm exec changeset statusopenwiki patch bump detected.
  • git diff --check — passed.

Coverage after the full runs: 89.39% statements, 83.67% branches, 94.32%
functions, and 89.67% lines.

@changeset-bot

changeset-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1f5d9f5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
openwiki Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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.

[Bug]: CLAUDE.md symlinked to AGENTS.md — concurrent snippet writes clobber each other and corrupt the managed block (since 0.3.3)

1 participant