Repository navigation
fix(init): create Claude config dir for global init - #2521
Conversation
|
This pull request has been automatically marked as stale due to 90 days of inactivity. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. Your contribution is very much appreciated — we're sorry we haven't been able to review it yet. |
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@rtk-release-bot this is still relevant and ready. Merged current |
Co-authored-by: Cursor <cursoragent@cursor.com>
develop split src/hooks/init.rs into src/hooks/init/. The change now sits in init/mod.rs (ensure_parent_dir, called from write_if_changed) and init/claude.rs (before the settings.json update_json_file, outside dry-run), with the regression tests in init/claude.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KuSh
left a comment
There was a problem hiding this comment.
Round 1 of 3 — APPROVED, follow-ups filed
Claim: rtk init -g (default, --hook-only, --no-patch) creates the missing Claude config directory before its atomic writes instead of failing on a fresh home, while --dry-run still creates nothing.
Scope: accept (round 1, frozen)
Ran: rtk init × 3,064 forms (13 agents + --codex/--copilot/--opencode/--gemini/--claude-md/--hook-only, local/-g, install/uninstall, policy, dry-run, -v, run twice + uninstall) × 6 seeds (empty home, pre-existing user config, custom env dirs, empty env vars, empty/BOM/invalid/null JSON, pre-existing .bak) — develop c50ff30d (after the #3878 split) vs fixup fe3ba462; plus nested and relative CLAUDE_CONFIG_DIR, ~/.claude as a file, read-only HOME. Invariants: non-default targets (CLAUDE_CONFIG_DIR), every decision (create / update / dry-run / uninstall), under the hook (rtk rewrite passes rtk init through unchanged). Savings: n/a, no output filter touched.
Matrix: never — ran this round
CI @fe3ba462: all green (11/11, including test on ubuntu, macos and windows)
Blocks merge (0, frozen at round 1)
None.
The matrix shows 158 diffs, all where they should be. Every exit-code diff goes from 1 to 0: init -g, init -g --auto-patch, init -g --no-patch, init -g --hook-only --auto-patch (± -v) on an empty home, and init -g with CLAUDE_CONFIG_DIR="". There are no diffs at all when ~/.claude already exists, and no crashes.
Optional — will not hold merge
with_missing_claude_dir_overridesetsHOMEfor the whole test process. It is the only in-processHOMEmutation in the lib tests, andtemp_envserialises only its own callers. A parallel test readingdirs::home_dir()twice (for exampletee_file::test_display_shell_path_uses_home_var_for_home_paths_with_spaces) could see it change in between. I could not trigger this in 800 runs, so it is a note, not a finding. Only the OpenCode test needsHOME; the other three could drop it.--dry-rundoes not mention that the real run will create the config directory.ensure_pi_extensions_dirprints[dry-run] would create …for the same situation.
Filed as follow-ups
-
#4247
CLAUDE_CONFIG_DIRis used as given when relative or holding a literal~— the resolver is outside this PR's hunks. Before this PR,CLAUDE_CONFIG_DIR='~/.claude' rtk init -gfailed (exit 1). Now it creates./~/.claude/in the current directory and exits 0. Develop already writes to a relativeCLAUDE_CONFIG_DIRwhenever it exists, so the fix belongs inresolve_claude_dir_from, not here. -
--agent cursor -gon a fresh home now gets past~/.claudeand fails on the missing~/.cursor(exit 1 before and after). That is #2097, with #3000 / #3290 open against it. -
Creating the parent in one shared place (
update_json_file/atomic_write) instead of per agent is planned as a separate follow-up PR once this lands, so this PR stays scoped to Claude.
Checked and correct — no need to re-verify
- Both new call sites are after every dry-run return. The update and
allow_read_errorbranches requirepath.exists(), so they cannot run with a missing parent. - Every other
write_if_changedcaller (Hermes, Codex, OpenCode, Gemini, Vibe, Copilot) already creates its directory, so for them the newcreate_dir_allis a no-op. - Tests: removing either
ensure_parent_dircall, or dropping the!dry_rungate on the settings.json one, each makes one of the new tests fail. ~/.claudeas a file and a read-only HOME giveFailed to create directory: …(exit 1) instead of the temp-file error.- Uninstall leaves
settings.jsonand.bakin~/.claude, the same as develop when the directory already existed.
Hypotheses built and dropped
- A dangling
~/.claude/RTK.mdsymlink gets replaced by a regular file: the parent exists, so the addedcreate_dir_allis a no-op and the path is byte-for-byte develop's. - The new tests write the real config on Windows or with
XDG_CONFIG_HOME: develop already has seven ungatedrun_default_mode/run_hook_only_modetests with the same exposure, and this PR makes it no worse. - Test
unwrap(): it matches the surrounding test module.
Your questions
None asked.
Next
Fixup fe3ba46 pushed. It merges develop, where #3878 split src/hooks/init.rs into src/hooks/init/, and carries the change over unchanged in substance:
ensure_parent_diris ininit/mod.rs, called fromwrite_if_changed.- The settings.json call is in
init/claude.rs, beforeupdate_json_file. It is gated on!dry_runthere, becauseupdate_json_filenow handles dry-run itself. - The four tests are in
init/claude.rs. The OpenCode test imports its path constants locally, so thecfg(unix)gate leaves nothing unused on Windows.
fmt, clippy and all 3,959 tests pass; a Windows cargo check --all-targets is clean. Approving once CI is green on it.
Rounds: 1/3. Threads: 0 open, 0 resolved this round.
Summary
Fixes #2519.
Context
Plain global init can currently fail on a fresh machine or user profile when ~/.claude does not exist:
The underlying issue is that atomic_write() creates its tempfile inside the target file's parent directory. That parent directory must exist before NamedTempFile::new_in(parent) is called.
Related: #1840 fixes the separate Cursor-only path for
rtk init -g --agent cursorand missing ~/.cursor. This PR intentionally stays focused on the default/global Claude init path.CONTRIBUTING.md alignment
fix(init): create Claude config dir for global init.develop.Test plan
cargo fmt --all --checkcargo test missing_claude_dir -- --nocapturecargo test --all(2212 passed, 8 ignored)cargo clippy --all-targets -- -D warningsHOMEandCLAUDE_CONFIG_DIR:./target/debug/rtk init -g --auto-patch