Skip to content

feat(minimal): warn and confirm before uploading from non-VCS directories - #790

Merged
0chroma merged 6 commits into
mainfrom
0chroma/feat-vcs-upload-confirm-770
Jul 21, 2026
Merged

feat(minimal): warn and confirm before uploading from non-VCS directories#790
0chroma merged 6 commits into
mainfrom
0chroma/feat-vcs-upload-confirm-770

Conversation

@0chroma

@0chroma 0chroma commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

When activating a session from a directory that is not a version-control repository root, the CLI now warns the user and asks for confirmation before the recursive file upload. This prevents accidentally uploading large directory trees (e.g. a home directory) when running minimal activate from the wrong place.

  • Adds is_vcs_root(&Path) -> bool to detect Git, Mercurial, SVN, CVS, and Jujutsu repository roots (including Git worktree .git files)
  • In cmd_activate, if the project path is not a VCS root and stdin is a TTY, prompts the user before uploading; declining skips the upload and starts the session with an empty workspace
  • Non-interactive contexts (CI, pipes, agents) skip the prompt and proceed with the upload as before
  • --sync none remains available for explicit opt-out

Closes #770.

Summary by CodeRabbit

  • New Features
    • Added detection of common version-control repository roots (including Git worktrees/submodules).
    • For tarball sync, the upload source now uses the nearest project configuration directory (when available).
  • Bug Fixes
    • If a configuration is present but malformed, activation now fails instead of falling back.
    • Confirmation for non-repository sources is now shown only in interactive terminal runs.
    • If confirmation is declined, the session workspace starts empty.
  • Tests
    • Added coverage for repository-root detection, upload-root resolution, and malformed configuration handling.

Note

Warn and prompt before uploading from non-VCS directories in cmd_activate

  • Adds is_vcs_root in file_upload.rs to detect VCS roots by checking for .git, .hg, .svn, CVS, and .jj markers (supports .git as file or directory).
  • Adds resolve_upload_root in lib.rs to walk up the directory tree and find the nearest minimal.toml, using its repo root as the upload directory instead of the invocation path.
  • In --sync tarball mode, cmd_activate now warns the user and prompts for confirmation when the upload root is not a VCS root; on decline, the upload is skipped and the workspace starts empty.
  • Non-interactive stdin bypasses the prompt and proceeds without confirmation.
  • Behavioral Change: uploads now originate from the resolved project root rather than the current working directory when a minimal.toml is found above it.

Changes since #790 opened

  • Changed resolve_upload_root function to return Result<Utf8PathBuf, anyhow::Error> and propagate non-NotFound mfile errors [b2bbefd]
  • Updated cmd_activate async function to handle resolve_upload_root Result and modified confirmation prompt default [b2bbefd]
  • Updated tests for resolve_upload_root to accommodate Result return type and error expectations [b2bbefd]
  • Moved upload root resolution in cmd_activate to execute before daemon connection establishment [f64bbd9]
  • Changed the conditional check in cmd_activate async function from !std::io::stdin().is_terminal() to !can_prompt_interactively() when determining whether to skip user confirmation prompts before uploading files from non-VCS root directories [b739b94]
📊 Macroscope summarized c07be15. 2 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

…ries

When activating a session from a directory that isn't a VCS root,
prompt the user for confirmation before the recursive file upload to
prevent accidentally uploading large trees like a home directory.
Detection covers Git, Mercurial, SVN, CVS, and Jujutsu. Non-interactive
contexts (CI, pipes, agents) skip the prompt and proceed.

Closes #770.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Tarball activation now resolves the project root, detects common VCS markers, and conditionally uploads workspace files. Non-VCS roots prompt for confirmation interactively, while non-interactive execution proceeds without prompting. Malformed project files cause activation to fail.

Changes

VCS-aware workspace upload

Layer / File(s) Summary
VCS root detection and coverage
crates/minimal/src/file_upload.rs
Adds VCS_MARKERS and exported is_vcs_root, with tests for Git, Mercurial, Subversion, CVS, Jujutsu, and plain directories.
Project-root resolution and conditional tarball upload
crates/minimal/src/lib.rs
Resolves the nearest minimal.toml, propagates malformed-file errors, and conditionally uploads the resolved root after VCS checks and interactive confirmation. Tests cover missing, root-level, nested, and malformed project files.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant cmd_activate
  participant resolve_upload_root
  participant file_upload_is_vcs_root
  participant InteractiveConfirmation
  participant upload_workspace_files
  cmd_activate->>resolve_upload_root: resolve project root
  resolve_upload_root-->>cmd_activate: upload root or error
  cmd_activate->>file_upload_is_vcs_root: check upload root
  file_upload_is_vcs_root-->>cmd_activate: VCS result
  alt non-VCS root and interactive
    cmd_activate->>InteractiveConfirmation: request upload confirmation
    InteractiveConfirmation-->>cmd_activate: confirmation result
  end
  alt upload permitted
    cmd_activate->>upload_workspace_files: upload resolved root
  end
Loading

Possibly related PRs

Poem

A rabbit finds the project root,
Checks VCS beneath the boot.
“Upload?” it asks when markers hide;
A yes sends files, a no leaves space inside.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The summary is clear, but the required Testing and Checklist sections from the template are missing. Add a Testing section with commands or output and a Checklist section covering docs updates and any BREAKING CHANGE footer.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy #770 by detecting supported VCS roots and prompting before recursive uploads from non-VCS directories.
Out of Scope Changes check ✅ Passed The extra root-resolution and error-propagation changes are directly related to the upload-warning behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title is concise, conventional, and accurately summarizes the main change to warn before uploading from non-VCS directories.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

Comment thread crates/minimal/src/lib.rs Outdated
let should_upload = file_upload::is_vcs_root(utf8_path.as_std_path())
|| !std::io::stdin().is_terminal()
|| confirm(&format!(
"{utf8_path} is not a version control repository root. \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is the utf8_path one of:

  • the CWD with a minimal.toml / .minimal/minimal.toml
  • if above doesn't hold, an ancestor dir that has one of those minimal.toml files
  • if none of the above hold the CWD

Ie it reflects the previous CLIs search alg?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a good point, we probably want to walk the tree and always do the upload/thing from the root of the project.

For doing that for the mfile you can run mfile::File::from_dir_recursive then you can call repo_path() to get the base. https://github.com/gominimal/minimal/blob/main/crates/mfile/src/lib.rs#L652

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ahh good catch yes

Tarball sync uploaded from whatever directory the user ran
`minimal activate` in, so activating from a subdir uploaded
only the subdir. Resolve the upload root the same way the CLI
discovers config — walking up to the nearest minimal.toml —
and use that for both the VCS check and the upload. Falls back
to the input path when no config is found.

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@crates/minimal/src/lib.rs`:
- Around line 1002-1009: The upload guard’s file_upload::is_vcs_root check
accepts nested CVS directories based only on marker presence, bypassing
confirmation. Update is_vcs_root to validate an actual checkout root rather than
any nested VCS metadata directory, and add coverage for nested CVS directories
while preserving recognized root behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5d8b2ad3-7fff-406c-84dc-de6cbf6ef6bc

📥 Commits

Reviewing files that changed from the base of the PR and between b2a584d and c4836d1.

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

Comment thread crates/minimal/src/lib.rs Outdated
Comment on lines +1002 to +1009
// Guard against accidentally uploading a non-VCS directory
// (e.g. `~`): if the resolved project root is not a recognized
// VCS root, warn and ask for confirmation before the recursive
// upload. On non-interactive stdin (CI, pipes, agents) we
// proceed without prompting — `--sync none` remains available
// for explicit opt-out (#770).
let should_upload = file_upload::is_vcs_root(upload_root.as_std_path())
|| !std::io::stdin().is_terminal()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Validate actual VCS roots, not just marker presence.

At Line 1008, a nested CVS working directory is treated as a repository root because it contains CVS/. With no mfile, that bypasses this PR’s confirmation despite uploading from a non-root directory. Make the detector VCS-aware enough to distinguish checkout roots, and cover nested CVS directories.

🤖 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 1002 - 1009, The upload guard’s
file_upload::is_vcs_root check accepts nested CVS directories based only on
marker presence, bypassing confirmation. Update is_vcs_root to validate an
actual checkout root rather than any nested VCS metadata directory, and add
coverage for nested CVS directories while preserving recognized root behavior.

…ad-confirm-770

# Conflicts:
#	crates/minimal/src/lib.rs
Comment thread crates/minimal/src/lib.rs
resolve_upload_root caught every from_dir_recursive error, so a
malformed minimal.toml in an ancestor silently fell back to the
invocation subdir. The daemon never saw the broken file, fabricated
a default config, and activation proceeded with the wrong setup.
Only fall back on NotFound; propagate parse and I/O errors.
Comment thread crates/minimal/src/lib.rs

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

Actionable comments posted: 2

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)

1076-1094: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Resolve and confirm before creating the session.

CreateSession has already succeeded when these new fallible calls run. A malformed ancestor config or stdin read error returns through ? without requesting session cleanup, leaving a created but unconfigured session. Compute the upload root and confirmation decision before connecting to the daemon; upload only after an ID exists.

🤖 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 1076 - 1094, Move the fallible
upload-root resolution and confirmation logic currently using
resolve_upload_root and confirm before the CreateSession/daemon connection flow.
Preserve the VCS-root check and non-interactive behavior, then create the
session only after validation succeeds and perform the upload once the session
ID exists, ensuring errors before session creation cannot leave an unconfigured
session.
🧹 Nitpick comments (1)
crates/minimal/src/lib.rs (1)

2136-2144: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Exercise the malformed ancestor case.

The contract is specifically about a broken config found while walking upward. Resolve from a nested child directory so the test catches errors being swallowed during traversal.

Proposed test adjustment
         std::fs::write(dir.path().join(mfile::MFILE_NAME), "not valid toml = =").unwrap();
-        let path = camino::Utf8Path::from_path(dir.path()).expect("temp path is UTF-8");
-        assert!(resolve_upload_root(path).is_err());
+        let root = camino::Utf8Path::from_path(dir.path()).expect("temp path is UTF-8");
+        let child = root.join("nested");
+        std::fs::create_dir_all(&child).unwrap();
+        assert!(resolve_upload_root(&child).is_err());
🤖 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 2136 - 2144, Update
resolve_upload_root_errors_on_malformed_mfile to create a nested child directory
beneath the directory containing the malformed mfile, then call
resolve_upload_root with that child path. Keep the existing assertion that
resolution returns an error, ensuring the upward traversal propagates the
malformed ancestor configuration failure.
🤖 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.

Inline comments:
In `@crates/minimal/src/lib.rs`:
- Around line 1086-1094: Update the should_upload decision to use
can_prompt_interactively() instead of checking only whether stdin is a terminal,
while preserving the VCS-root shortcut and confirmation behavior. This ensures
confirm is called only when the prompt can be visibly displayed, matching the
later prompt policy.
- Line 1076: Update the async flow around resolve_upload_root so its synchronous
recursive filesystem work runs inside tokio::task::spawn_blocking rather than on
the Tokio worker. Await the blocking task, propagate both filesystem and
task-join errors appropriately, and preserve the resolved upload_root behavior.

---

Outside diff comments:
In `@crates/minimal/src/lib.rs`:
- Around line 1076-1094: Move the fallible upload-root resolution and
confirmation logic currently using resolve_upload_root and confirm before the
CreateSession/daemon connection flow. Preserve the VCS-root check and
non-interactive behavior, then create the session only after validation succeeds
and perform the upload once the session ID exists, ensuring errors before
session creation cannot leave an unconfigured session.

---

Nitpick comments:
In `@crates/minimal/src/lib.rs`:
- Around line 2136-2144: Update resolve_upload_root_errors_on_malformed_mfile to
create a nested child directory beneath the directory containing the malformed
mfile, then call resolve_upload_root with that child path. Keep the existing
assertion that resolution returns an error, ensuring the upward traversal
propagates the malformed ancestor configuration failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 368271af-800e-45ef-9011-068aa65212a5

📥 Commits

Reviewing files that changed from the base of the PR and between c07be15 and b2bbefd.

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

Comment thread crates/minimal/src/lib.rs Outdated
// `minimal activate ./subdir` still uploads the whole
// project. Falls back to `utf8_path` when no mfile is found
// anywhere up the tree (#770).
let upload_root = resolve_upload_root(&utf8_path)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that this crate already enables Tokio's blocking-task support.
fd '^Cargo\.toml$' crates/minimal -x sed -n '1,220p' {}
rg -n -C2 '\bspawn_blocking\s*\(' crates/minimal/src

Repository: gominimal/minimal

Length of output: 3272


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant areas in crates/minimal/src/lib.rs
ast-grep outline crates/minimal/src/lib.rs --view expanded >/tmp/minimal_lib_outline.txt
sed -n '1,220p' /tmp/minimal_lib_outline.txt

# Show the call site around the reported line and the helper definition.
rg -n -C 8 'resolve_upload_root|cmd_activate|upload_root' crates/minimal/src/lib.rs

# If the helper is in another file, locate it.
rg -n 'fn resolve_upload_root|resolve_upload_root\(' crates/minimal/src

Repository: gominimal/minimal

Length of output: 16656


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect how the Tokio runtime is created and whether cmd_activate runs on a worker thread.
rg -n -C 3 '#\[tokio::main|tokio::runtime::Builder|run\(cli\)|cmd_activate\(' crates/minimal/src

# Show the main entrypoint for more context.
sed -n '1,140p' crates/minimal/src/main.rs

# Show the beginning of run() and run_command() to understand execution context.
sed -n '470,520p' crates/minimal/src/lib.rs

Repository: gominimal/minimal

Length of output: 6000


Move resolve_upload_root off the async worker crates/minimal/src/lib.rs:1076 still does a synchronous recursive filesystem walk here; wrap it in tokio::task::spawn_blocking so a slow or wedged filesystem can't block the Tokio runtime.

🤖 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` at line 1076, Update the async flow around
resolve_upload_root so its synchronous recursive filesystem work runs inside
tokio::task::spawn_blocking rather than on the Tokio worker. Await the blocking
task, propagate both filesystem and task-join errors appropriately, and preserve
the resolved upload_root behavior.

Source: Learnings

Comment thread crates/minimal/src/lib.rs
resolve_upload_root ran after CreateSession, so a malformed mfile in
an ancestor errored out without aborting the daemon-side session.
Move the call before the daemon connection so it fails before any
session is created.
@0chroma
0chroma enabled auto-merge (squash) July 21, 2026 20:39
confirm() writes the prompt to stderr, so checking only stdin leaves
the command waiting on an invisible prompt when stderr is redirected.
Use can_prompt_interactively() — already used by the policy prompt
path — which checks both.
@0chroma
0chroma merged commit 81011b5 into main Jul 21, 2026
29 checks passed
@0chroma
0chroma deleted the 0chroma/feat-vcs-upload-confirm-770 branch July 21, 2026 21:03
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.

Warn and Ask to Upload Files When A Session is Started when CWD is not a VCS Root

3 participants