Skip to content

fix(trash): preserve dangling symlinks across filesystems - #117

Merged
steipete merged 1 commit into
mainfrom
fix/linux-environment-failures
Aug 3, 2026
Merged

fix(trash): preserve dangling symlinks across filesystems#117
steipete merged 1 commit into
mainfrom
fix/linux-environment-failures

Conversation

@steipete

@steipete steipete commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • recreate symbolic links explicitly during cross-filesystem trash fallback so dangling targets are not dereferenced
  • force the dangling-symlink regression through the EXDEV copy path
  • remove ambient-umask assumptions from the affected archive and symlink-parent mode assertions

Linux reproduction

Tested unmodified origin/main in an unprivileged Debian 12 arm64 container with Node 22.23.2, pnpm 10.34.5, and a locally built native binding.

  • TMPDIR=/dev/shm pnpm test test/edge-coverage.test.ts -t "moves broken symlinks to trash" failed with ENOENT at src/trash.ts:166 because /dev/shm and the home trash are different filesystems.
  • umask 0027; pnpm test exposed three ambient-mode assertions: the ZIP staging assertion and two symlink-parent fixture-mode assertions.
  • The ordinary Linux suite under umask 0022 passed on the unmodified baseline, confirming why these cases stay hidden in common environments.

After the fix, the full suite passed with both conditions active: TMPDIR=/dev/shm, umask 0027, 1,061 tests passed and 26 platform tests skipped.

Verification

  • pnpm check
  • pnpm test:security (70 passed)
  • git diff --check
  • Codex autoreview: clean, no accepted/actionable findings

The Linux proof used OrbStack's local arm64 container backend. It did not cover x86_64 Linux or a remote crabbox host.

@steipete
steipete requested a review from a team as a code owner August 3, 2026 06:45
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 Urgent regression or broken agent/channel workflow affecting real users now. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Aug 3, 2026
@clawsweeper

clawsweeper Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed August 3, 2026, 2:48 AM ET / 06:48 UTC.

ClawSweeper review

What this changes

The PR recreates symbolic links rather than dereferencing them in the cross-filesystem trash fallback, adds forced-EXDEV dangling-link coverage, and removes ambient-umask assumptions from three tests.

Merge readiness

⚠️ Ready for maintainer review - 2 items remain

This PR addresses a real current-release defect in the cross-filesystem trash fallback: current main follows a dangling symlink during copy and fails before it can remove the original link. The patch preserves the existing target-identity checks, adds focused EXDEV coverage, and includes credible real Linux proof; wait for the failed Node 24 Windows check to be explained or repaired before merge. Likely related people: steipete (high confidence), who introduced the current fallback and authored this correction.

Priority: P1
Reviewed head: cdbb504e823ff5f73a6ef38eaeb007cd52acc043

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) This is a focused, well-proven repair to a security-sensitive path; the outstanding Windows CI result remains the final merge gate.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The PR body reports an after-fix Debian container run with both the forced cross-filesystem condition and restrictive umask, including a passing full suite, check, and security suite; redact any private environment details in any future posted logs.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body reports an after-fix Debian container run with both the forced cross-filesystem condition and restrictive umask, including a passing full suite, check, and security suite; redact any private environment details in any future posted logs.
Evidence reviewed 5 items Current defect on main: After an EXDEV rename failure, current main invokes cpSync on the source path. For a dangling symlink, that copy path dereferences the absent target and fails instead of moving the link itself.
Focused proposed repair: The branch distinguishes a symlink using the captured lstat result, reads its link text, rechecks the target identity, and recreates the link at the already-reserved destination; ordinary files retain the existing cpSync path.
Regression exercises the affected branch: The changed test forces renameSync to return EXDEV for a dangling symlink, then verifies that the destination is still a link to the missing target and the source no longer exists.
Findings None None.
Security None None.

How this fits together

The trash helper moves a caller-supplied path from an allowed filesystem root into a private trash directory under the user home directory. It first performs an identity-guarded rename and, only for cross-filesystem moves, uses a copy-and-guarded-delete fallback.

flowchart LR
  A[Caller path] --> B[Allowed-root and identity guard]
  B --> C[Private trash destination]
  C --> D{Guarded rename}
  D -->|Succeeds| E[Moved entry]
  D -->|EXDEV| F[Copy fallback]
  F --> G[Identity recheck and guarded removal]
  G --> E
Loading

Before merge

  • Resolve merge risk (P1) - One Node 24 Windows check is failing and the Windows bundled-package/native checks were still in progress in the provided PR state. The failure details were not available from the read-only environment, so merge should wait for a maintainer or CI rerun to establish whether it is unrelated or needs a platform repair.
  • Complete next step (P2) - The source repair is sound, but a failed Windows required check needs human/CI result review before this security-sensitive change can merge.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Patch scope 5 files affected; production +10/-1, tests +31/-17 The implementation change is limited to the EXDEV trash fallback, with most of the diff providing targeted regression and environment-stability coverage.

Merge-risk options

Maintainer options:

  1. Resolve the Windows validation result (recommended)
    Inspect the failed Node 24 Windows job and either correct any patch-related platform regression or confirm with fresh required checks that it is unrelated before merging.
  2. Pause for a clean platform run
    Hold this security-sensitive filesystem change until all required Windows and native checks have reached a known result.

Technical review

Best possible solution:

Land the explicit readlink-and-recreate fallback after the Windows validation result is resolved, keeping the identity rechecks and forced-EXDEV regression coverage intact.

Do we have a high-confidence way to reproduce the issue?

Yes. Forcing the guarded rename to fail with EXDEV for a dangling symlink reaches current main's cpSync fallback, whose source-level behavior attempts to follow the missing target; the branch's regression test captures that exact path.

Is this the best way to solve the issue?

Yes. Recreating only the already-identified symlink from readlink, while retaining the existing identity checks and ordinary cpSync path, is the narrowest maintainable fix.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against b8738eb59b29.

Labels

Label changes:

  • add P1: A shipped core filesystem operation fails for allowed dangling symlinks whenever the rename crosses filesystem boundaries.
  • add merge-risk: 🚨 security-boundary: The patch changes symlink handling inside a guarded filesystem fallback, where identity preservation and non-dereference behavior are security contracts.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body reports an after-fix Debian container run with both the forced cross-filesystem condition and restrictive umask, including a passing full suite, check, and security suite; redact any private environment details in any future posted logs.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body reports an after-fix Debian container run with both the forced cross-filesystem condition and restrictive umask, including a passing full suite, check, and security suite; redact any private environment details in any future posted logs.

Label justifications:

  • P1: A shipped core filesystem operation fails for allowed dangling symlinks whenever the rename crosses filesystem boundaries.
  • merge-risk: 🚨 security-boundary: The patch changes symlink handling inside a guarded filesystem fallback, where identity preservation and non-dereference behavior are security contracts.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body reports an after-fix Debian container run with both the forced cross-filesystem condition and restrictive umask, including a passing full suite, check, and security suite; redact any private environment details in any future posted logs.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body reports an after-fix Debian container run with both the forced cross-filesystem condition and restrictive umask, including a passing full suite, check, and security suite; redact any private environment details in any future posted logs.

Evidence

What I checked:

  • Current defect on main: After an EXDEV rename failure, current main invokes cpSync on the source path. For a dangling symlink, that copy path dereferences the absent target and fails instead of moving the link itself. (src/trash.ts:166, b8738eb59b29)
  • Focused proposed repair: The branch distinguishes a symlink using the captured lstat result, reads its link text, rechecks the target identity, and recreates the link at the already-reserved destination; ordinary files retain the existing cpSync path. (src/trash.ts:149, cdbb504e823f)
  • Regression exercises the affected branch: The changed test forces renameSync to return EXDEV for a dangling symlink, then verifies that the destination is still a link to the missing target and the source no longer exists. (test/edge-coverage.test.ts:286, cdbb504e823f)
  • History and release provenance: Blame attributes the current fallback to the v0.5.2 release commit, establishing that the defect is in the latest shipped baseline rather than already fixed on main. (src/trash.ts:149, 32759e8923a1)
  • Repository-policy alignment: The branch adds an Unreleased changelog entry and a narrow regression test, matching the repository policy for public filesystem-safety corrections. (CHANGELOG.md:5, cdbb504e823f)

Likely related people:

  • steipete: Git blame attributes the current EXDEV fallback to Peter Steinberger in the v0.5.2 release commit; the same contributor authored the focused correction. (role: current fallback author and recent area contributor; confidence: high; commits: 32759e8923a1, cdbb504e823f; files: src/trash.ts, test/edge-coverage.test.ts)
  • ctbritt: History for the trash and edge-coverage area also includes Chris Allbritton's guarded-path work, which is adjacent to the identity-boundary behavior preserved here. (role: adjacent filesystem-safety contributor; confidence: medium; commits: 8cced2ab6264; files: src/trash.ts, test/edge-coverage.test.ts)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Resolve or classify the failed Node 24 Windows check after the remaining Windows/native jobs complete.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@steipete
steipete force-pushed the fix/linux-environment-failures branch from cdbb504 to 33e25d0 Compare August 3, 2026 06:55
@clawsweeper

clawsweeper Bot commented Aug 3, 2026

Copy link
Copy Markdown

ClawSweeper status: review started.

I am starting a fresh review of this pull request: fix(trash): preserve dangling symlinks across filesystems This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

@steipete
steipete merged commit 5f528c3 into main Aug 3, 2026
22 checks passed
@steipete
steipete deleted the fix/linux-environment-failures branch August 3, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant