Repository navigation
Fix lock rewrites changing file owner (#1635) - #1636
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Hosted and vendored rewrites (and rollback / remove / vendor --revert) replace a lockfile or manifest by staging a new file and renaming it over the old one. The stage kept the old permission bits but not the owner, so a run as root (a Docker bind mount, sudo) left poetry.lock owned by root, and the user's next `poetry add` failed with "Permission denied". A run in a shared group checkout moved the file to the runner's primary group. The shared stage-and-rename writer now gives the stage the old file's owner and group before the rename (before the chmod, so setuid bits survive), best-effort like npm's write-file-atomic: an unprivileged run keeps the group when it can and otherwise behaves as before. Fixes #1635 Assisted-by: Claude Code:claude-opus-5-5
A durable lockfile write now syncs the restored owner along with the content, so a crash right after the rename can't leave the file with the runner's owner. Refs #1635 Assisted-by: Claude Code:claude-opus-5-5
Spell out in the CLI contract that only existing project files (lockfiles, manifests, .npmrc) keep their owner, not socket-patch's own .socket/ records, and stop the root-only test from assuming a new file's group, which BSD and setgid directories set differently. Refs #1635 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Labeled Ready for review (burn-down agent).
Generated by Claude Code |
Port of #1639. uv 0.13.2 refuses the pylock.toml that Pipenv 2026.8.0 writes (environments = []), so the pipenv e2e leg fails on main and kept this PR out of the merge queue. Pin setup-uv to 0.13.1 until that is sorted out; this no-ops once #1639 lands. Refs #1635 Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Dequeued by Generated by Claude Code |
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e68bb85. Configure here.
|
Wenxin Jiang (@Wenxin-Jiang) this PR changed after your approval (on Besides merges from main, the only new commit is e68bb85: the CI-only uv pin from #1639 ( Generated by Claude Code |
|
Ready for review re-confirmed at the new head (burn-down agent).
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1635
Summary
A hosted or vendored scan run as root (a Docker bind mount,
sudo) used to leave the project'spoetry.lockowned by root, so the owner's nextpoetry add/poetry lockfailed withPermission denied. A run in a shared group checkout moved the lock to the runner's primary group. Rewritten lockfiles, manifests and.npmrcnow keep their owner and group, the same as their mode.Root cause
Every mode-preserving rewrite of a project file (
poetry.lock,package.json,.npmrc,Gemfile.lock, …, plus therollback/remove/vendor --revertrestores and the group-commit replay) runs throughstage_and_rename_blockingincrates/socket-patch-core/src/utils/fs.rs. It stages a fresh file, copies only the old file's permission bits onto it, and renames it over the old path. The fresh inode belongs to the process's euid/egid. So the defect isn't Poetry-specific: every ecosystem's lock and manifest rewrite had it.Fix
keep_ownerfchowns the stage to the old file's uid/gid before the fsync and before the chmod (chown(2)clears setuid/setgid), best-effort like npm's write-file-atomic. An unprivileged caller that can't give the file away (EPERM) still keeps the group when it belongs to it; otherwise the file keeps the runner's ownership, as before.CLI_CONTRACT.mdnow states the ownership rule.Test evidence
utils::fs::tests::preserving_writes_keep_the_destination_owner(root; every preserving writer, setuid mode kept)(0, 0, 2540)vs(1000, 1001, 2540)utils::fs::tests::preserving_write_keeps_a_supplementary_group(run as a non-root user in adevsgroup)in_process_redirect::root_hosted_scan_keeps_the_lock_owner(end-to-end hosted scan as root)(0, 0)vs(1000, 1001)Per-issue checklist:
poetry add/poetry lock#1635: root run keeps the lock's owner (preserving_writes_keep_the_destination_owner,root_hosted_scan_keeps_the_lock_owner); a group-checkout run keeps the group (preserving_write_keeps_a_supplementary_group).Commands run locally:
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --all-features --lib: 6536 passed, 4 failed.cargo test -p socket-patch-cli --all-features --lib --test in_process_redirect --test in_process_redirect_pipenv: 3 failed inin_process_redirect. All 7 failures are read-only-directory tests that root ignores. They fail the same way onmainas root, and they pass as a non-root user against this branch. The sandbox runs as root.cargo test --workspacebuild ran out of the sandbox's disk allowance, so CI runs the rest.Review notes (
/code-review high)Fixed: the
WriteOpts::preserve_modedoc, a root-only test that assumed a new file's group, and the contract's scope wording. Not fixed, on purpose:.socket/manifest.jsonand the vendored ledgers use the plain (non-preserving) writer, and files a root run creates (a new.npmrc,.socket/vendor/dirs) are root-owned. The issue is about existing project files, and those writers don't preserve the mode either. Possible follow-up: give new files their parent directory's owner, asrestore_file_permissionsdoes for patched files.fchowncalls stay silent. Reporting them likeOWNERSHIP_NOT_RESTORED_MARKERwould need a warning channel through every writer.https://claude.ai/code/session_01XwH2o4SRLPwRpbu8QBzz2T
Generated by Claude Code