Skip to content

fix(sftp): preserve concurrent files during local publication - #3286

Merged
binaricat merged 10 commits into
mainfrom
codex/sftp-local-publish-safety
Sep 6, 2026
Merged

fix(sftp): preserve concurrent files during local publication#3286
binaricat merged 10 commits into
mainfrom
codex/sftp-local-publish-safety

Conversation

@binaricat

@binaricat binaricat commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Summary

Local download publication or rollback could overwrite a file another process had just saved. Later pathname-based timestamp updates could also modify a concurrent replacement. Publication and restoration now share one exclusive operation, preserving recovery artifacts on conflicts.

Type of Change

  • Bug fix
  • New feature
  • Refactor / code cleanup
  • Documentation update
  • Build / CI change
  • Other (please describe):

Related Issue (optional)

Related to overwrite and metadata safety in #3186. Reporter permission behavior is not claimed fully reproduced.

Changes Made

  • Use exclusive hardlink publication, with an owned exclusive-copy fallback when links are unsupported; never unlink a pathname that may belong to another writer.
  • Treat publication as the commit boundary and preserve prepared/backup artifacts on incomplete copies or close failures.
  • Prepare timestamps before publication and restrictive chmod. Other local metadata updates verify and stamp the same file handle.
  • Retain a prepared read handle for restrictive-mode copies; stamp write-only destinations without creating or truncating a path.
  • Retry metadata preservation through the identity-checked published handle when preparation fails or times out; mark preparation complete only after a successful stamp.
  • Preserve readable original access for safe rollback. If an unreadable original cannot be restored without replacing another writer, stop before moving it and retain its contents and mode.

Screenshots / Demo

Real filesystem regressions cover publication/restoration replacement races, late cancellation, timestamps, mode 000, final-path replacement and close failure. Publication/bridge/SCP focused checks passed; a real loopback download matched its source.

Testing

  • I have tested these changes locally (npm run dev)
  • Linting passes (npm run lint)
  • Tests pass (npm test)
  • Generated capability tool specs are updated when applicable (npm run generate:capability-tools)
  • No new console errors or warnings, if this affects app behavior

All five audit branches were combined in an isolated checkout: 11,469 passed, 0 failed, 18 skipped. Lint and production build passed. Each fix and its follow-ups received independent reviews. These are local results; GitHub checks report their current state separately.

The exclusive-copy fallback can expose partial bytes while copying; it preserves recoverable data but is not atomic visibility. Physical FAT/exFAT hardware was unavailable.

Full evidence and architecture comparison: SFTP audit report.

Checklist

  • My code follows the existing project style
  • I have added or updated relevant documentation
  • I have not introduced any breaking changes (or I have described them above)

@binaricat binaricat added the automation:codex-loop Own/bot PR waiting on Codex review↔fix loop label Sep 6, 2026
@binaricat

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-06T06:28:32.397308Z a5bbaa1 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de4412c9d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread electron/bridges/localFilePublish.cjs Outdated
@binaricat

Copy link
Copy Markdown
Owner Author

@codex review

Repository owner deleted a comment from binaricat Sep 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb9e4ee4be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread electron/bridges/transferBridge.cjs
@binaricat

Copy link
Copy Markdown
Owner Author

@codex review

Repository owner deleted a comment from binaricat Sep 6, 2026
@binaricat
binaricat marked this pull request as ready for review September 6, 2026 05:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b32ec68fd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread electron/bridges/transferBridge.cjs Outdated
if (transfer.publishedLocalIdentity) {
try {
const currentStat = await fs.promises.lstat(transfer.targetPath);
if (!currentStat.isFile() || stableLocalFileIdentity(currentStat) !== transfer.publishedLocalIdentity) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Compare published identities in the same representation

publishLocalFileExclusive() returns { dev, ino, size }, and both staged-transfer callbacks store that object in transfer.publishedLocalIdentity, while stableLocalFileIdentity(currentStat) returns a string. This strict comparison therefore always fails for real promoted downloads, so resumable and SCP downloads never receive the source mtime and subsequent size+mtime skip-unchanged checks repeatedly transfer them; normalize the returned or stored identity before comparing.

Useful? React with 👍 / 👎.

Comment thread electron/bridges/transferBridge.cjs Outdated
Comment on lines +1101 to +1102
const currentStat = await fs.promises.lstat(transfer.targetPath);
if (!currentStat.isFile() || stableLocalFileIdentity(currentStat) !== transfer.publishedLocalIdentity) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Follow supported destination symlinks for the identity check

When a download targets an existing symlink, inspectLocalPromotionTarget() deliberately resolves and publishes to its regular-file target, but this lstat() observes the symlink itself. Consequently currentStat.isFile() is false and mtime preservation is skipped even after the identity representation is corrected, causing symlink-targeted folder downloads to miss skip-unchanged matches; verify the resolved target (while retaining the concurrent-change protection) instead.

Useful? React with 👍 / 👎.

Comment thread electron/bridges/transferBridge.cjs Outdated
// own timestamps (Codex P2 on the commit boundary).
if (transfer.publishedLocalIdentity) {
try {
const currentStat = await fs.promises.lstat(transfer.targetPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stamp the verified inode rather than its pathname

If another process replaces the destination after this identity check resolves but before fs.promises.utimes() executes, the pathname-based call still stamps that concurrent file with the transfer source's timestamps. The fresh evidence is that the newly added guard performs lstat and utimes as separate pathname operations, leaving the same check-to-use window; open and verify a handle before applying times through that handle, or stamp the prepared inode before publication.

Useful? React with 👍 / 👎.

@binaricat

Copy link
Copy Markdown
Owner Author

@codex review

@binaricat
binaricat marked this pull request as draft September 6, 2026 05:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec1868a069

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread electron/bridges/localFilePublish.cjs Outdated
Comment thread electron/bridges/transferBridge.cjs Outdated
@binaricat
binaricat marked this pull request as ready for review September 6, 2026 05:18
@binaricat

Copy link
Copy Markdown
Owner Author

@codex review

Repository owner deleted a comment from binaricat Sep 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d6c487fc6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread electron/bridges/transferBridge.cjs Outdated
Repository owner deleted a comment from binaricat Sep 6, 2026
@binaricat

Copy link
Copy Markdown
Owner Author

@codex review

@binaricat
binaricat marked this pull request as draft September 6, 2026 05:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7639bd83d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread electron/bridges/transferBridge.cjs Outdated
Comment thread electron/bridges/transferBridge.localPublish.test.cjs
@netcatty-bot

Copy link
Copy Markdown
Collaborator

The automatic Codex fix was preserved, but it did not pass verification. The PR remains draft for maintainer review.

The candidate patch and verification report were preserved as codex-fix-patch-34015273615.

View this run: https://github.com/binaricat/Netcatty/actions/runs/34015273615

@netcatty-bot netcatty-bot added ready-for-human Requires human implementation and removed automation:codex-loop Own/bot PR waiting on Codex review↔fix loop labels Sep 6, 2026
Repository owner deleted a comment from binaricat Sep 6, 2026
@binaricat
binaricat marked this pull request as ready for review September 6, 2026 06:23
@binaricat
binaricat merged commit e2a9e54 into main Sep 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Requires human implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants