Repository navigation
Conversation
This was referenced Sep 26, 2026
📊 Automated PR Analysis
SummaryConsolidates every file write performed by Review Checklist
Analyzed automatically by wshm · This is an automated analysis, not a human review. |
4 tasks done
KuSh
force-pushed
the
fix/init-shared-parent-dir
branch
2 times, most recently
from
September 28, 2026 01:26
fb52d9c to
b1ae978
Compare
KuSh
force-pushed
the
fix/init-shared-parent-dir
branch
from
October 5, 2026 22:08
b1ae978 to
b4807c2
Compare
…ry-run` The Codex uninstall removed the rtk-instructions block from AGENTS.md without checking --dry-run, so a dry run printed "Nothing written" after rewriting the file. It now reports the block it would remove. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When ~/.trae-cn is a symlink to ~/.trae, `rtk init -g --agent trae` treated the two hooks.json paths as separate files and patched the same file twice. The second pass backed up RTK's own first write over the user's hooks.json.bak, so the user's backup was lost. The alias is now detected and the file patched once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backup taken before a config is patched was a plain copy onto <name>.bak, and a copy opens its destination: a .bak that is a symlink made it write outside the project, or into another project file, and a hard link wrote into the file it shares. A repository can ship either. The copy now goes to a temp file created with the source's permission bits and renamed over the .bak, so whatever sits there is replaced rather than written through. A .bak this user cannot write is still refused, and where the directory takes no new file an existing .bak is rewritten in place without following a link, unless it is a hard link to the file or to another one. Fixes rtk-ai#4157. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each agent created its own directories before writing, some files went through atomic_write and others through fs::write, several agents carried their own copy of the backup-then-replace logic, and only Codex kept its hook inside the project. Every init write now goes through one of three shared functions, and the checks the call sites did around them live there, decided once per write so that the write, the check before a consent prompt and the --dry-run preview agree: - RTK's own files, written whole by RTK (RTK.md, GEMINI.md, hook scripts, extensions, plugins including Antigravity's, the Kilo Code rules file, the filters templates), are replaced atomically through a temp file renamed over the target, with no backup. - Config files RTK edits but does not own (settings.json, the hooks configs, Vibe's hooks.toml, Hermes' config.yaml) are backed up to <name>.bak, then replaced atomically: agents re-read them while they run. Gemini's settings.json now takes that backup too. - Instruction files RTK adds to but does not own (CLAUDE.md, AGENTS.md, Copilot instructions, .clinerules, .windsurfrules) are backed up to <name>.bak, then written in place, so they keep their owner, group, ACLs and hard links; the cost is that a crash mid-write is recovered from the backup rather than avoided. An earlier <name>.bak -- the file before RTK touched it, or the user's own copy -- is kept rather than replaced. Where the directory takes no new file, the write goes ahead without a backup and says so; a config whose backup cannot be written is refused instead. A project file that resolves outside the project is not backed up into it. A file patched twice in one run is backed up once, so the backup holds the file as it was before RTK ran. All three create the missing parent directories, so the per-agent create_dir_all blocks are gone. All three refuse a file this user cannot write, as fs::write does, before prompting or backing up. A Claude, Gemini or Vibe config RTK cannot patch is not asked about and does not stop the run, --auto-patch included: it is left to the manual instructions, as a declined prompt is. On Unix a new or replaced file gets the permission bits a plain write would, instead of 0600. A dangling symlink is followed exactly when a plain write would (the directory it names exists); directories are never created through a dangling link, and one whose directory does not exist is refused instead of being replaced by a regular file (RTK used to replace some such links and fail on others). In project mode, writes stay inside the project (rtk-ai#4156): - Hook configs and extensions (Codex, Copilot, Trae, Droid, Pi, OMP, Antigravity, and their backups) that resolve outside the project are refused before anything is read or written, on install and uninstall; a Copilot uninstall still removes its instructions block and then reports the hook it left in place. This generalises the guard Codex already had; the symlink walk moves from codex.rs to symlinks.rs, and Trae's CN alias check now uses the same resolver as the write. - Any other project write that resolves outside the project, or (on Unix) that edits in place a file with other hard links, is named first; then --auto-patch writes it, the default mode asks, and --no-patch or a non-interactive run refuses it. The check runs where the write happens, so a file that needs no change is never asked about, a file written twice is asked about once, and --dry-run says what the real run would do. --codex now accepts --auto-patch and --no-patch for this. A non-interactive project `rtk init` therefore stops when a file it would edit resolves outside the project (for example a CLAUDE.md linked to a shared copy), unless --auto-patch is given; a read-only file is refused in every mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each write site printed its own "[dry-run] would …" line after preview(), and its own "[ok] …" or verbose line after the write. write_reported() now takes the path, the write kind and a Report holding both messages, and prints them itself: - in a dry run, the "would" line (and the content under -v) is printed only when the real run would write; a refused or unconfirmed write no longer shows a "would refuse" line followed by a "would write" line - after a real write, the done line goes to stdout or, for verbose-only lines, to stderr under -v, and the backup path is shown under -v for every write that took one - the global filters template dry run now previews like the others atomic_write, patch_config and patch_instructions are folded into write_file(path, kind, content). ProjectScope is marked #[must_use]: it is a guard, and a call that does not bind it would end the scope before any write ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KuSh
force-pushed
the
fix/init-shared-parent-dir
branch
from
October 7, 2026 00:31
b4807c2 to
cbdb69d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every
rtk initwrite now goes through one path insrc/hooks/init/write.rs, with the checks each agent used to do (or skip) around it. A singleplan_writedecides, for each write, what it lands on, what refuses it, what must be confirmed and what happens to the backup; the write, the check made before a consent prompt and the--dry-runpreview all read that one plan.Four commits, each standing on its own:
rtk init --codex --uninstall --dry-run— the dry run removed the rtk-instructions block for real.~/.trae-cnthat links to~/.traeonly once — the second pass backed RTK's own write up over the user'shooks.json.bak..bak— the copy goes to a temp file renamed over the.bak, so a planted symlink or hard link is replaced rather than written through. Fixes fs::copy onto a .bak path follows a symlink there, so a cloned repo can redirect rtk init writes outside the project #4157.<name>.bak, then atomic. Gemini's settings.json now gets a backup too.<name>.bak(an earlier one is kept), then written in place, so owner, group, ACLs and hard links survive.create_dir_allblocks are gone.--auto-patchwrites it, the default mode asks,--no-patchor a non-interactive run refuses it.--codexnow accepts--auto-patch/--no-patchfor this.Fixes #4156. Fixes #4157.
Behaviour changes worth knowing
rtk initstops when a file it would edit resolves outside the project (e.g. aCLAUDE.mdlinked to a shared copy), unless--auto-patchis given. A read-only file is refused in every mode.<name>.baknext to them.Related PRs
.bakinstead of a no-follow open, which also covers hard links and needs no platform flags.AGENTS.mdgets the warn / ask / refuse policy above rather than a hard refusal.pi.rs,extension_paths_aliasnow uses the shared resolver) and fix(hooks): own Gemini hooks by exact command, not substring #4235 (gemini.rs).Test plan
cargo fmt --all && cargo clippy --all-targets && cargo test --allon each commit and on the head (4041 tests)cfg(windows)build cross-checkedunshare -r), for CI containers running as rootrtk initmatrix against develop: 8576 forms (every agent, scope,--uninstall,--auto-patch/--no-patch,--dry-run,-v) over 16 setups (empty home, user content, legacy blocks, dangling links, links out of the project for hook and instruction files, a shared directory, read-only files, aliased Pi/OMP and Trae CN, …): no crash, no write under--dry-run, no write outside the project without--auto-patch, and every other difference is one listed above🤖 Generated with Claude Code