Skip to content

fix(import): guard file collection against symlink cycles (#358)#361

Merged
luongnv89 merged 2 commits into
mainfrom
fix/358-guard-collectfiles-symlink-cycles
Jul 11, 2026
Merged

fix(import): guard file collection against symlink cycles (#358)#361
luongnv89 merged 2 commits into
mainfrom
fix/358-guard-collectfiles-symlink-cycles

Conversation

@luongnv89

Copy link
Copy Markdown
Owner

Closes #358

Summary

Prevent collectFiles() from re-entering directory symlinks that resolve to a directory already active in the current traversal, eliminating hangs and stack/path overflows during import conflict comparison, diff rendering, and mtime scans.

Approach

Selected Option 2 — balanced cycle-safe traversal. Resolve each directory to its canonical realpath, track canonical paths active in the current recursion chain, and skip recursive re-entry. Remove paths after their subtree completes so distinct non-cyclic symlink aliases remain independently visible to file comparison.

Decision Record

  • Root cause: collectFiles() followed directory symlinks through stat() and recursively called walk() without tracking canonical directories. A self- or ancestor-referential link repeatedly re-entered the same directory until filesystem path limits or resource exhaustion interrupted the import operation.
  • Options considered: Option 1 — add a fixed recursion-depth cap; Option 2 — track realpath-resolved directories in the active traversal; Option 3 — replace importer traversal with a shared filesystem-walker abstraction.
  • Options rejected: Option 1 — an arbitrary cap can reject valid deep skill trees and does not identify cycles; Option 3 — broadens a low-complexity bug fix into an unnecessary cross-module refactor.
  • Selected option: Option 2 — active realpath tracking with focused regression coverage.
  • Residual risk: none identified; canonical keys are normalized case-insensitively on Windows, and completed paths are removed so non-cyclic aliases retain prior behavior.
  • Reproduction: npx vitest run src/import-skills.test.ts -t "terminates when identical trees contain self-referential symlinks" --testTimeout=5000 confirmed red for the stated reason (expected false to be true) → regression test src/import-skills.test.ts now passes.

Analyzed at: fix/358-guard-collectfiles-symlink-cycles @ b6f715c (2026-07-11)

Changes

File Change
src/importer.ts Track realpath-resolved directories active in collectFiles() recursion, skip cyclic re-entry, and preserve traversal through completed non-cyclic aliases.
src/import-skills.test.ts Add cross-platform regression tests for self-referential cycles and multiple non-cyclic symlinks sharing one target.

Test Results

  • Unit tests: 1,885 passed (npm test)
  • Integration tests: covered by the unit/CLI suite
  • E2e tests: 73 passed (npx vitest run tests/e2e/node-e2e.test.ts)
  • Typecheck: passed (npm run typecheck)
  • Build: passed (npm run build and pre-push hook)
  • QA cycles: 2

Acceptance Criteria Verification

Criterion Status Evidence
Directory traversal terminates when a skill tree contains a symlink cycle. pass Verified red: npx vitest run src/import-skills.test.ts -t "terminates when identical trees contain self-referential symlinks" --testTimeout=5000 → fixed → src/import-skills.test.ts regression test passes.
Files reachable through normal, non-cyclic directories continue to be collected. pass Existing nested-tree test remains green; new compares each non-cyclic symlink path when targets are shared test verifies completed aliases remain traversable.
A regression test covers a self-referential or ancestor-pointing directory symlink. pass src/import-skills.test.ts creates self-links through the cross-platform createDirSymlink() helper and confirms identical cyclic trees terminate and compare equal.

@luongnv89
luongnv89 merged commit 1cdee2b into main Jul 11, 2026
13 checks passed
@luongnv89
luongnv89 deleted the fix/358-guard-collectfiles-symlink-cycles branch July 11, 2026 05:59
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.

import: guard collectFiles against directory-symlink cycles

1 participant