Skip to content

fix: refuse to overwrite existing minimal.toml without --force - #1053

Merged
norrietaylor merged 2 commits into
mainfrom
inbox-patch/init-overwrite-guard-13f1897f9fe157d9
Jul 29, 2026
Merged

fix: refuse to overwrite existing minimal.toml without --force#1053
norrietaylor merged 2 commits into
mainfrom
inbox-patch/init-overwrite-guard-13f1897f9fe157d9

Conversation

@gominimal-aw-bot

@gominimal-aw-bot gominimal-aw-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #1032
Routing-Key: inbox-route/I_kwDOSUhdos8AAAABKmgbEQ

run_init_flow wrote minimal.toml unconditionally and reported "Created" even when it replaced an existing file; on non-TTY stdin confirm() returns its default true on EOF, so min init -y and --no-input silently overwrote an edited config with no backup. This adds an existence check that refuses the overwrite unless the new --force flag is passed, mirroring min session destroy --all, which already refuses non-interactively and names the flag to proceed (the shared helper was extracted in #682; informed by #682). The plan banner and result line now report "overwrite"/"Updated" when replacing; fresh-create behaviour is unchanged, matching the issue's scope. No natural unit-test hook exists — the helper needs a live graph checkout — so the guard is proven by the verification gate below.

Verification

cargo fmt --all --check — clean
cargo clippy --workspace --locked -- -D warnings — 0 warnings
cargo build --workspace --locked — ok
cargo test --workspace --locked — ok (minimal crate 131 passed; 0 failed across workspace)

Generated by inbox-patch ·

Note

Refuse to overwrite existing minimal.toml without --force in min init

  • run_init_flow in lib.rs now checks if minimal.toml already exists and exits with an error unless --force is passed.
  • Adds a force field to InitArgs and threads it through cmd_init into run_init_flow.
  • Confirmation and completion messages now say "Will overwrite" / "Updated" when the file exists, and "Will create" / "Created" when it does not.
  • Behavioral Change: running min init on a directory with an existing minimal.toml now fails by default instead of prompting to overwrite.

Macroscope summarized 3cb8f52.

Summary by CodeRabbit

  • New Features

    • Added a --force option to min init, allowing existing configuration files to be overwritten.
    • Initialization now clearly indicates whether it will create or overwrite the configuration.
  • Bug Fixes

    • Prevented accidental overwrites by stopping initialization when a configuration file already exists unless --force is provided.

run_init_flow wrote minimal.toml unconditionally and reported
"Created" even when it replaced an existing file. On non-TTY stdin
confirm() read EOF as its default true, so `min init -y` and
`--no-input` silently overwrote a user's edited config with no backup.

Add an existence check that refuses the overwrite unless the new
--force flag is passed, mirroring `min session destroy --all`, which
already refuses non-interactively and names the flag to proceed. The
plan banner and result line now report "overwrite"/"Updated" when an
existing file is being replaced.

Refs: #1032
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

min init now accepts --force to overwrite an existing minimal.toml. Without force, initialization aborts when the file exists. Confirmation and completion messages distinguish creation from updating, while activation scaffolding disables overwrites.

Changes

Init overwrite control

Layer / File(s) Summary
Init CLI flag and wiring
crates/minimal/src/lib.rs
InitArgs adds --force, and cmd_init passes both confirmation and force settings to run_init_flow.
Existing-file handling
crates/minimal/src/lib.rs
run_init_flow rejects existing files unless forced, updates create/overwrite messaging, and activation scaffolding invokes it with force disabled.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: 0chroma

Poem

A rabbit checks the config with care,
No careless overwrite hiding there.
“Force” may update what came before,
While create and update now speak more.
Hop safely, little init hare!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code matches #1032 by refusing existing minimal.toml overwrites unless --force is set and preserving fresh-create behavior.
Out of Scope Changes check ✅ Passed The changes stay focused on min init overwrite protection and message updates; no unrelated scope appears introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is concise and accurately summarizes the main change: preventing overwrites of existing minimal.toml unless --force is used.
Description check ✅ Passed The description covers the change and verification well, but it doesn't match the template exactly: it uses 'Verification' instead of 'Testing' and omits the checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/minimal/src/lib.rs (1)

2533-2555: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make the no-force write atomic.

A file can be created after exists() and before std::fs::write()—especially while the confirmation prompt is open—then be truncated without --force. Use create_new(true) for the non-force path so the filesystem enforces the no-overwrite policy.

Proposed fix
-    std::fs::write(&plan.toml_path, &plan.content)
-        .with_context(|| format!("writing {}", plan.toml_path.display()))?;
+    if force {
+        std::fs::write(&plan.toml_path, &plan.content)
+            .with_context(|| format!("writing {}", plan.toml_path.display()))?;
+    } else {
+        use std::io::Write as _;
+
+        let mut output = match std::fs::OpenOptions::new()
+            .write(true)
+            .create_new(true)
+            .open(&plan.toml_path)
+        {
+            Ok(output) => output,
+            Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => bail!(
+                "refusing to overwrite existing {} without --force",
+                plan.toml_path.display()
+            ),
+            Err(error) => {
+                return Err(error).with_context(|| format!("writing {}", plan.toml_path.display()))
+            }
+        };
+        output
+            .write_all(plan.content.as_bytes())
+            .with_context(|| format!("writing {}", plan.toml_path.display()))?;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimal/src/lib.rs` around lines 2533 - 2555, Update the write flow
around the `exists` check and `std::fs::write` so non-force writes use an atomic
create-new operation that fails if the target appears after the check or
confirmation prompt. Preserve overwrite behavior when `force` is enabled, and
retain the existing `with_context` error reporting for the write operation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/minimal/src/lib.rs`:
- Around line 2533-2555: Update the write flow around the `exists` check and
`std::fs::write` so non-force writes use an atomic create-new operation that
fails if the target appears after the check or confirmation prompt. Preserve
overwrite behavior when `force` is enabled, and retain the existing
`with_context` error reporting for the write operation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a59d1ffe-e07b-4215-a26a-1342cfc25bf7

📥 Commits

Reviewing files that changed from the base of the PR and between c1d466c and 6a82e33.

📒 Files selected for processing (1)
  • crates/minimal/src/lib.rs

@norrietaylor
norrietaylor enabled auto-merge (squash) July 29, 2026 18:52
@norrietaylor
norrietaylor merged commit f36045f into main Jul 29, 2026
29 checks passed
@norrietaylor
norrietaylor deleted the inbox-patch/init-overwrite-guard-13f1897f9fe157d9 branch July 29, 2026 19:13
norrietaylor added a commit that referenced this pull request Jul 29, 2026
The merge commit c0f9a4f resolved conflicts in crates/minimal/src/lib.rs
by taking the pre-merge side, silently reverting two fixes that had
landed on main:

- #1053 — the `min init` refuse-to-overwrite guard: `InitArgs::force`,
  the bail on an existing `minimal.toml`, and the Updated/Created
  wording. Without it `min init` overwrites an existing `minimal.toml`
  with no backup and no prompt, which is the data loss #1032 was filed
  for.
- #1060 — the `SHELL=/bin/sh` pin on the ssh ProxyCommand and the
  `SendEnv` forwarding of LANG/LC_*/TZ. Without the pin, attach dies at
  "banner exchange ... Broken pipe" for any user whose $SHELL is a bare
  name (fish) or absent from the ssh context.

Neither revert was caught by the suite: there is no test for the init
guard, and `interactive_attach_requires_a_tty_on_stdin` only asserts the
error contains "not a TTY" and "--command", which holds either way.

Rebuilt lib.rs from main and reapplied only the intended change, so the
diff against main is now exactly the `hide = true` on
`AttachArgs::command` plus the non-TTY error reword: +4/-3, was +9/-51.

Verified: rustfmt clean; `cargo build -p minimal --locked` ok;
`min session attach --help` no longer lists `-c`/`--command` while
`--command` still parses; `min init --help` lists `--force` again.
`cargo test -p minimal` cannot run on macOS (dev-deps pull minimald ->
procfs/caps, Linux-only), so the suite is left to CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
norrietaylor added a commit that referenced this pull request Jul 29, 2026
…surface (#1071)

* chore(minimal): hide `min session attach --command` from help

Add `hide = true` to the `#[arg(long, short)]` attribute on
`AttachArgs::command` so `-c` / `--command` no longer appear in
`min session attach --help`, `-h`, or shell completions. The flag
stays functional for existing scripted callers; the option promises a
general remote exec it cannot deliver (no PTY, only three daemon
commands accepted), so it should not be advertised for discovery.

The non-TTY attach error still points stuck callers at `--command`;
reword it to frame the flag as a deliberate hidden escape hatch rather
than drop the reference, keeping the advice actionable now that the
flag is off the help surface.

* fix(minimal): restore #1053 and #1060 clobbered by the merge commit

The merge commit c0f9a4f resolved conflicts in crates/minimal/src/lib.rs
by taking the pre-merge side, silently reverting two fixes that had
landed on main:

- #1053 — the `min init` refuse-to-overwrite guard: `InitArgs::force`,
  the bail on an existing `minimal.toml`, and the Updated/Created
  wording. Without it `min init` overwrites an existing `minimal.toml`
  with no backup and no prompt, which is the data loss #1032 was filed
  for.
- #1060 — the `SHELL=/bin/sh` pin on the ssh ProxyCommand and the
  `SendEnv` forwarding of LANG/LC_*/TZ. Without the pin, attach dies at
  "banner exchange ... Broken pipe" for any user whose $SHELL is a bare
  name (fish) or absent from the ssh context.

Neither revert was caught by the suite: there is no test for the init
guard, and `interactive_attach_requires_a_tty_on_stdin` only asserts the
error contains "not a TTY" and "--command", which holds either way.

Rebuilt lib.rs from main and reapplied only the intended change, so the
diff against main is now exactly the `hide = true` on
`AttachArgs::command` plus the non-TTY error reword: +4/-3, was +9/-51.

Verified: rustfmt clean; `cargo build -p minimal --locked` ok;
`min session attach --help` no longer lists `-c`/`--command` while
`--command` still parses; `min init --help` lists `--force` again.
`cargo test -p minimal` cannot run on macOS (dev-deps pull minimald ->
procfs/caps, Linux-only), so the suite is left to CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com>
Co-authored-by: Norrie Taylor <91171431+norrietaylor@users.noreply.github.com>
Co-authored-by: Norrie Taylor <norrie@minimal.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

min init destroys an existing minimal.toml with no backup; --no-input is never read and confirm() treats EOF as yes

1 participant