Skip to content

fix(atomic): preserve long destination filenames - #191

Merged
steipete merged 1 commit into
mainfrom
codex/qa-20260901-private-staging-names
Sep 1, 2026
Merged

fix(atomic): preserve long destination filenames#191
steipete merged 1 commit into
mainfrom
codex/qa-20260901-private-staging-names

Conversation

@steipete

@steipete steipete commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes an issue where consumers writing or extracting valid long filenames would receive ENAMETOOLONG, or a wrapped filesystem error, because an internal temporary filename copied the entire destination basename before adding a random suffix.

On current main 790472f73bb499ec8eb0ee7a823de1e98b0a94c0, this reproduces for 200/240-byte ZIP basenames, 240-byte Root.write and Root.copyIn basenames, and 240-byte synchronous JSON basenames. Each regression first creates and reads the destination directly to establish that the filesystem accepts it.

Why This Change Was Made

Private staging names now use a fixed prefix and UUID independent of the destination. Archive extraction reuses its existing bounded default. JSON staging preserves literal parent segments and drive-relative semantics rather than normalizing the path through a different parent.

The repair leaves public producer/callback filenames, custom tempPrefix, final destinations, exports, configuration, permissions, and identity checks unchanged. The existing retained-descriptor, publication, and cleanup logic remains responsible for safety. A partial-write test now identifies write handles by their flags and asserts that a short write actually occurred, so it no longer depends on an internal filename containing the destination.

Production: +10/−12, net −2. Tests: +59/−1, net +58. Docs: +12/−3, net +9.

User Impact

Consumers can use long filenames accepted by the filesystem without private staging consuming extra filename space. Short-name controls and the public naming contracts retain their behavior. No new fallback, setting, dependency, or migration is introduced. Release-note context is recorded here; changelog changes remain release-owned.

Evidence

  • On the same current-main checkout, the 11 selected regressions changed from 5 failed / 6 passed to 11 passed. The failures occur at private staging creation; the tests also verify replacement contents and absence of leaked temporary siblings.
  • The 24 owner/security files passed 461 tests, with 113 platform/native-dependent skips.
  • Canonical pnpm check:changed (the repository's pnpm check alias) passed lint, build, documentation checks, the full suite (4,026 passed / 2,333 skipped), and real root-tarball consumer installation/import/public-API checks. No dependency or lockfile changes and no test retries.
  • Fresh independent managed P0–P2 review of the complete current twelve-file diff found no actionable findings.
  • Native Windows execution was not performed locally. The separate native-platform pnpm package:smoke requires a prebuilt host .node payload that is absent locally; its normal CI jobs remain required before landing. The packed-consumer check above is not claimed as native-platform proof.

Focused command before and after:

pnpm test test/archive.test.ts test/fs-safe.test.ts test/json.test.ts \
  -t filesystem-admitted --reporter=json --outputFile=results.json
pnpm check:changed

Environment: macOS arm64, Node 24.20.0, pnpm 11.24.0. Introduction provenance is unknown.

Keep private staging names independent of destination basenames across
archive extraction, guarded writes, native Windows writes, and sync JSON.
Reuse the existing archive default instead of adding a destination prefix.

Preserve public producer filenames, custom temporary prefixes, literal JSON
parent segments, and the existing identity and publication checks.
@clawsweeper

clawsweeper Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Sep 1, 2026
@clawsweeper

clawsweeper Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed September 1, 2026, 7:02 AM ET / 11:02 UTC.

ClawSweeper review

What this changes

The PR replaces destination-derived private staging names with bounded UUID-based names across archive extraction, Root writes, Windows native writes, and synchronous JSON writes, with long-filename regressions.

Merge readiness

⚠️ Needs maintainer review before merge - 2 items remain

Keep open: the fix addresses a source-proven long-filename failure, but this public package behavior change lacks the required Unreleased changelog entry.

Priority: P2
Reviewed head: 7d89d0c5c745fa24e00ec105803268da4f36890f

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The focused repair and real-path evidence are solid, but the required public release-note update remains before merge.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (terminal): The pinned PR body reports a real macOS run of the changed archive, Root-write/copy, and synchronous JSON paths after the fix, with eleven selected long-name regressions passing after a five-failure baseline; Windows-native execution remains for its required CI jobs.
Patch quality 🦐 gold shrimp (3/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The pinned PR body reports a real macOS run of the changed archive, Root-write/copy, and synchronous JSON paths after the fix, with eleven selected long-name regressions passing after a five-failure baseline; Windows-native execution remains for its required CI jobs.
Evidence reviewed 7 items Current main retains the fault: The pinned main parent derives synchronous JSON staging from the complete destination pathname, so a filesystem-admitted long basename gains a UUID suffix before creation.
Other guarded paths have the same basename budget issue: The pinned main parent also embeds the destination basename in fallback Root-write and ZIP-extraction temporary names.
Introduced repair preserves the guarded flow: The new fallback staging name is UUID-only while existing exclusive creation, descriptor identity checks, parent guarding, and rename verification remain in place.
Findings 1 actionable finding [P2] Document the public fix in the Unreleased changelog
Security None None.

How this fits together

Atomic writes and archive extraction stage content beside the destination, validate it, then rename it into place. The private staging filename must fit the filesystem independently of the final user-selected filename.

flowchart LR
  A[Destination basename] --> B[Atomic write or archive extraction]
  B --> C[Private staging filename]
  C --> D[Guarded content write]
  D --> E[Identity-checked rename]
  E --> F[Published destination]
  G[Long-name regression tests] --> B
Loading

Before merge

  • Document the public fix in the Unreleased changelog (P2) - This changes whether consumers can write filesystem-admitted long filenames, but no CHANGELOG.md entry is included. Repository policy requires an Unreleased entry for public operational changes; add a concise 0.7.1 note before merge.
  • Complete next step (P2) - A one-file mechanical follow-up can satisfy the repository's required release-note policy without changing the implementation.

Findings

  • [P2] Document the public fix in the Unreleased changelog — src/root-impl.ts:786
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production versus regression delta production +10/-12; tests +59/-1; docs +12/-3 The behavioral implementation is small and the regression coverage spans all affected public write paths.

Technical review

Best possible solution:

Add a concise 0.7.1 Unreleased entry describing support for filesystem-admitted long destination filenames, then retain the bounded private staging implementation and its regressions.

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

Yes: current-main source appends staging suffixes to full destination basenames, and the added regressions establish direct filesystem admission before exercising the affected APIs. This review did not execute the tests.

Is this the best way to solve the issue?

Yes, aside from the required release note: decoupling private temporary names from destination names is narrower than restricting valid destinations and retains the existing guarded publication design.

Full review comments:

  • [P2] Document the public fix in the Unreleased changelog — src/root-impl.ts:786
    This changes whether consumers can write filesystem-admitted long filenames, but no CHANGELOG.md entry is included. Repository policy requires an Unreleased entry for public operational changes; add a concise 0.7.1 note before merge.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

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

Labels

Label changes:

  • add P2: This fixes a bounded but real failure when consumers use filenames their filesystem already accepts.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The pinned PR body reports a real macOS run of the changed archive, Root-write/copy, and synchronous JSON paths after the fix, with eleven selected long-name regressions passing after a five-failure baseline; Windows-native execution remains for its required CI jobs.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The pinned PR body reports a real macOS run of the changed archive, Root-write/copy, and synchronous JSON paths after the fix, with eleven selected long-name regressions passing after a five-failure baseline; Windows-native execution remains for its required CI jobs.

Label justifications:

  • P2: This fixes a bounded but real failure when consumers use filenames their filesystem already accepts.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The pinned PR body reports a real macOS run of the changed archive, Root-write/copy, and synchronous JSON paths after the fix, with eleven selected long-name regressions passing after a five-failure baseline; Windows-native execution remains for its required CI jobs.
  • proof: sufficient: Contributor real behavior proof is sufficient. The pinned PR body reports a real macOS run of the changed archive, Root-write/copy, and synchronous JSON paths after the fix, with eleven selected long-name regressions passing after a five-failure baseline; Windows-native execution remains for its required CI jobs.

Evidence

Acceptance criteria:

  • [P1] pnpm check.
  • [P1] git diff --check.

What I checked:

  • Current main retains the fault: The pinned main parent derives synchronous JSON staging from the complete destination pathname, so a filesystem-admitted long basename gains a UUID suffix before creation. (src/json.ts:190, 790472f73bb4)
  • Other guarded paths have the same basename budget issue: The pinned main parent also embeds the destination basename in fallback Root-write and ZIP-extraction temporary names. (src/pinned-write.ts:234, 790472f73bb4)
  • Introduced repair preserves the guarded flow: The new fallback staging name is UUID-only while existing exclusive creation, descriptor identity checks, parent guarding, and rename verification remain in place. (src/pinned-write.ts:235, 7d89d0c5c745)
  • Regression coverage exercises the public paths: New tests first establish that the destination is admitted directly, then exercise ZIP extraction, Root.write, Root.copyIn, and writeJsonSync with long basenames and verify replacement plus cleanup. (test/fs-safe.test.ts:99, 7d89d0c5c745)
  • Repository policy requires release notes: AGENTS.md requires an Unreleased CHANGELOG entry for public, security, compatibility, package, or operational changes; the pinned introduced-file list has no CHANGELOG.md entry. (AGENTS.md:44, 7d89d0c5c745)
  • Feature-history routing: Recent merged work in the JSON, fallback-write, and archive paths is attributed to steipete, including the merged JSON identity hardening commit 12d9450. (src/json.ts:188, 12d945048db9)

Likely related people:

  • steipete: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rank-up moves

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

  • Add the concise Unreleased changelog entry for the corrected long-filename behavior.

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 marked this pull request as ready for review September 1, 2026 11:15
@steipete
steipete requested a review from a team as a code owner September 1, 2026 11:15
@steipete

steipete commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Merged as 8d4ff13365c33db031c4b7cbd48e40777a98733c. All twelve changed files match the reviewed head, and the task checkout is clean on main after a fast-forward pull. A fresh managed P0–P2 review found no actionable findings. Production: net −2 lines; tests: +58; docs: +9. No package version, tag, or npm publication was performed.

The long-filename repair retains the guarded publication and identity checks. All eleven selected regressions pass after five intended baseline failures; normal pnpm check:changed passed lint, build, docs, 4,026 tests, and the packed-consumer API check. CI at the exact PR head also passed the Node 22/24 matrix, Linux/macOS/Windows native checks, musl check, and bundled package smoke jobs. The new long-name tests run through their explicit fallback mode; this does not claim an additional long-name native Windows test that was not run.

The ClawSweeper changelog finding and matching rank-up move are intentionally skipped: changelog/version edits belong to release preparation. This repair PR preserves the release-note context in its body, and does not choose a future version, tag, or publish a package. The user-visible change is that filesystem-admitted destination basenames no longer overflow when private atomic-write/archive staging names are created. No remaining code or security finding was reported.

@clawsweeper

clawsweeper Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: fix(atomic): preserve long destination filenames 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 8d4ff13 into main Sep 1, 2026
29 checks passed
steipete added a commit that referenced this pull request Sep 1, 2026
Finalize lock reliability, native ZIP integrity, and long-filename release notes through PR #191. Align all package/crate versions and exact native pins, preserving historical notes and existing trusted-publishing behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant