Skip to content

link: cmux link seed push and seed clone, a Cloud project seed with the copy as fallback - #30634

Merged
teamleaderleo merged 4 commits into
feat-cmux-nextfrom
seed-push
Nov 1, 2026
Merged

teamleaderleo merged 4 commits into
feat-cmux-nextfrom
seed-push

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Piece 4 of the Cloud project seed (leo-workspace design/cloud.md, "Getting the project onto the machine"), the CLI half. Leo, on the start screen's line:

Your project folder is copied up.
we'll want to deal with the edge cases for this too

This adds cmux link seed push and cmux link seed clone, which put a project on a Cloud machine the way the design says: a git folder with a GitHub remote is seeded (the machine clones the remote, this Mac sends one bundle of what the remote lacks), and everything else takes the streamed copy. It builds on #30464 (the Mac-side plan), #30382 (the machine's git-seed-v1), #30427 (the copy fallback) and #30280 (the move pump's --sync). start-chat copy and the app switch to it in the next PR.

CLI

cmux link seed push --socket S LOCAL REMOTE [--since-last]
cmux link seed clone --socket S --repo OWNER/NAME [--ref REF] REMOTE
cmux link agent move ... --sync seed --sync-socket S --sync-local L --sync-remote R
  • push reads identify. A machine without fs-v1 answers machine_outdated. Otherwise it plans (seed::plan), uploads the bundle through fs-v1 to REMOTE/../.cmux-seed-<name>-<id>.bundle (inside the fs roots), calls git.seed.start, and reads git.seed.status once every 500 ms until the job ends or 45 minutes pass. That wait is a bounded loop inside the CLI process, not an agent polling loop. The upload is deleted afterwards either way.

  • Fallback to the copy (sync_cli::push, a full copy) happens when:

    • the plan answers a Fallback (not_git, not_top, no_commit, no_remote, ambiguous_remote, not_github, no_base, unmerged, submodule_local_work, git_failed);
    • the machine is another build without git-seed-v1 (machine_outdated);
    • the machine answers seed.needs_github, seed.base_missing or seed.clone_failed (or seed.unavailable).

    seed.base_missing first re-plans once from the remote default branch's tip (plan_with_base with Planned::bases' last). If the failed seed made the folder, the folder is removed before the copy. The answer then says mode:"copy", fallback_code and fallback_why. For needs_github it also carries install_url, so the app can offer the install while the chat goes on.

  • --since-last never falls back to a full copy:

    • After a seed, it sends a delta bundle (plan_since). Nothing changed: unchanged:true and no upload at all.
    • After a copy (mode copy), it re-pushes the copy to the same REMOTE, which the copy's manifest makes incremental.
    • No record, or a record from another machine: seed.no_previous. The machine's seed.since_mismatch and seed.machine_changed come back as errors.
  • Record: <state>/link-seed/<sha256(local NUL remote)>.json (0600, folder 0700), beside sync_cli's <state>/link-sync base record. It holds {mode, machine (identify's session_id), url, head, index, work}.

  • clone: the clone-only git.seed.start. It answers {ok, mode:"clone", head, head_ref}, or the error code plus install_url.

  • agent move --sync seed: once the chat is frozen, the pump calls seed_cli::push_since_last. An error gives the chat back, as --sync push does, with seed_cli::failure_code's code.

Output

stderr progress JSON lines (throttled to one per 250 ms, first and last always):

  • {"progress":{"phase":"upload","bytes_done","bytes_total"}} while the bundle uploads;
  • {"progress":{"phase":"clone|fetch|bundle|checkout|submodules|lfs","bytes_done","objects","objects_total"}} from the machine's job;
  • the copy's own {"progress":{"files_done","files_total","bytes_done","bytes_total"}} for a copy.

stdout, one line:

{"ok":true,"mode":"seed"|"delta"|"copy","unchanged":bool,"bundle_bytes":N,"head":SHA|null,"work":SHA|null,
 "skipped_files":[{"path","kind","why"}], "copy":{sync summary}?, "fallback_code"?, "fallback_why"?, "install_url"?}
{"ok":false,"error","error_code","install_url"?}

Test seam

git-seed-v1 got a debug-build-only seam, CMUX_TEST_GIT_SEED_GITHUB=<dir>. It makes the daemon's seeds clone https://github.com/O/N.git from file://<dir>/O/N.git with no credential. GitSeedService::with_test_github does the same for in-process tests. Release builds never read the variable, and nothing on the wire reaches it.

Tests (Linux, cmux-ci test)

  • tests/link_seed.rs: a real FsService and GitSeedService behind a socket, with a file:// bare repo as GitHub. It covers:
    • a full seed (unpushed commit, staged and untracked files, .env skipped by name, ignored folder left out, no bundle left behind), then a delta, then an unchanged delta that makes no fs.write;
    • --since-last errors: seed.no_previous (and nothing copied), seed.machine_changed, seed.since_mismatch;
    • fallback to the copy for a non-git folder (not_git, then a copy-mode --since-last re-push that writes only the new file), for a GitLab remote (not_github) and for a repo the machine cannot clone (seed.clone_failed);
    • a machine without git-seed-v1 (machine_outdated);
    • seed clone, ok and seed.clone_failed.
  • tests/cloud_move_journey.rs (two real daemons): the journey now seeds the dirty folder with cmux link seed push (mode seed, git status identical on the VM). It then moves the chat mid-turn with --sync seed, and the turn's last edit (src/turn.txt, written after the seed) arrives through the delta bundle.

Risk: the change is additive. start-chat copy and the app still call link sync push until the next PR. The only edit to existing behaviour is the agent move's --sync parse (push and pull unchanged) and two small sync_cli helpers (upload, is_refusal).

After review: the bundle is written in a private 0700 folder (git writes it world-readable under umask 022); a failed big upload's .cmux-sync-part is deleted too; seed clone asks for LFS files; a machine that does not answer identify is an error.

Before a fallback copy's own lines, stderr carries one {"progress":{"phase":"copy","fallback_code","install_url"?}}, so the card can say why while the copy runs.

Follow-ups: #30644 (--since-last checks the machine's folder before answering unchanged); a full re-seed into a folder that already holds a seed fetches into it instead of starting clean; submodules with local work still fall back to the copy (plan's submodule_local_work).

Agent session: agent session

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits November 1, 2026 01:58
… with a copy fallback; agent move --sync seed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 9aabf92: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr30634 -f ref=seed-push -f platform=macos -f backend_mode=local

Shipped in:

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): BLOCK at aad041c

BLOCK at aad041c

Visual verdict: no images; Leo tries the build

  1. [P1] [blocking] Temporary bundles expose private project contents. cmux-tui/crates/cmux-tui/src/link/seed_cli.rs:579 places the bundle directly in the system temp directory. Git creates it with default permissions: I reproduced mode 0644 under umask 022, even from a private repository. With shared /tmp, other users can read its contents throughout upload and polling. Create the bundle inside a private 0700 directory.

  2. [P2] [follow-up] Unchanged deltas bypass destination verification. cmux-tui/crates/cmux-tui/src/link/seed_cli.rs:324 returns success solely from the local snapshot. Deleting or modifying the remote checkout therefore goes undetected; an unavailable socket can also succeed because identify failures are ignored. Consequently, agent move --sync seed can proceed with a missing or divergent project. Verify remote seed state without uploading a bundle.

  3. [P2] [follow-up] Clone never requests LFS hydration. cmux-tui/crates/cmux-tui/src/link/seed_cli.rs:544 omits lfs, which defaults to false. The machine disables global/system Git configuration and skips its LFS step when this flag is false, so LFS repositories report successful clones containing pointer files. Enable LFS handling for clone-only requests.

  4. [P2] [follow-up] Failed multipart uploads leave bundles behind. cmux-tui/crates/cmux-tui/src/link/seed_cli.rs:477 deletes only the final target. Uploads larger than 8 MiB write to a separate .cmux-sync-part path until completion. An append or rename failure leaves that file behind, and retries generate new names, accumulating potentially large files. Clean up the staging path too.

Validation: static review and Git permission reproduction completed. Rust tests could not run because Cargo is unavailable.

Follow-ups:

  • Verify the destination for unchanged deltas.
  • Hydrate LFS files during clone.
  • Remove staging files after failed bundle uploads.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026

@cubic-dev-ai cubic-dev-ai 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.

8 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmux-tui/crates/cmux-tui-core/src/git_seed.rs">

<violation number="1" location="cmux-tui/crates/cmux-tui-core/src/git_seed.rs:52">
P3: A relative `CMUX_TEST_GIT_SEED_GITHUB` is ignored without any signal, so the seed quietly clones from real GitHub instead of the test directory. Warn or panic when the variable is set but not absolute, rather than filtering it out.</violation>
</file>

<file name="cmux-tui/crates/cmux-tui/tests/link_seed.rs">

<violation number="1" location="cmux-tui/crates/cmux-tui/tests/link_seed.rs:201">
P3: `files()` turns any read failure into an empty string, so binary or non-UTF-8 files compare equal regardless of content. Read as bytes (`std::fs::read`) and compare `Vec<u8>`, or unwrap the read.</violation>
</file>

<file name="cmux-tui/crates/cmux-tui-core/src/git_seed/job.rs">

<violation number="1" location="cmux-tui/crates/cmux-tui-core/src/git_seed/job.rs:193">
P3: `with_test_github` disables the GitHub credential path and enables `protocol.file`, but it is compiled into release builds. Gate it with `#[cfg(debug_assertions)]` to match the debug-only seam in `installed()`, so release builds cannot construct this service.</violation>

<violation number="2" location="cmux-tui/crates/cmux-tui-core/src/git_seed/job.rs:194">
P3: A non-UTF-8 absolute directory accepted by the environment seam is lossy-converted by `display()`, so the rewritten `file://` URL points elsewhere and seeding fails. Encode the OS path without loss or reject it.</violation>
</file>

<file name="cmux-tui/crates/cmux-tui/src/link/seed_cli.rs">

<violation number="1" location="cmux-tui/crates/cmux-tui/src/link/seed_cli.rs:225">
P3: The `--repo` check accepts empty owner or name parts such as `o/` and `/n`. Require both parts to be non-empty.</violation>

<violation number="2" location="cmux-tui/crates/cmux-tui/src/link/seed_cli.rs:461">
P2: A top-level REMOTE such as `/project` fails with `seed.bad_request` here instead of falling back to the copy. Parent-less folders should take the copy path, or `parse` should reject them.</violation>
</file>

<file name="cmux-tui/crates/cmux-tui/src/link/seed_cli/record.rs">

<violation number="1" location="cmux-tui/crates/cmux-tui/src/link/seed_cli/record.rs:91">
P3: The rename is not made durable because the parent folder is never synced. After a crash the seed record can revert to an older commit set or disappear, and `--since-last` then plans from stale state or reports `seed.no_previous`. Sync the parent folder after the rename, as `agent_cli/record.rs` does.</violation>
</file>

<file name="cmux-tui/crates/cmux-tui/src/link/sync_cli.rs">

<violation number="1" location="cmux-tui/crates/cmux-tui/src/link/sync_cli.rs:326">
P2: `upload` opens the bundle with `O_NOFOLLOW` but not `O_NONBLOCK`, so a FIFO swapped in at the bundle path blocks the open indefinitely. The shared `open()` helper and `place.rs` both add `O_NONBLOCK` for this reason; add it here too.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

let (parent, name) = remote
.rsplit_once('/')
.filter(|(parent, name)| !parent.is_empty() && !name.is_empty())
.ok_or_else(|| SeedError::new("seed.bad_request", "the machine folder has no parent"))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A top-level REMOTE such as /project fails with seed.bad_request here instead of falling back to the copy. Parent-less folders should take the copy path, or parse should reject them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/src/link/seed_cli.rs, line 461:

<comment>A top-level REMOTE such as `/project` fails with `seed.bad_request` here instead of falling back to the copy. Parent-less folders should take the copy path, or `parse` should reject them.</comment>

<file context>
@@ -0,0 +1,631 @@
+    let (parent, name) = remote
+        .rsplit_once('/')
+        .filter(|(parent, name)| !parent.is_empty() && !name.is_empty())
+        .ok_or_else(|| SeedError::new("seed.bad_request", "the machine folder has no parent"))?;
+    let target = format!("{parent}/.cmux-seed-{name}-{}.bundle", unique());
+    let total = planned.bundle_bytes;
</file context>

}
let file = std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: upload opens the bundle with O_NOFOLLOW but not O_NONBLOCK, so a FIFO swapped in at the bundle path blocks the open indefinitely. The shared open() helper and place.rs both add O_NONBLOCK for this reason; add it here too.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/src/link/sync_cli.rs, line 326:

<comment>`upload` opens the bundle with `O_NOFOLLOW` but not `O_NONBLOCK`, so a FIFO swapped in at the bundle path blocks the open indefinitely. The shared `open()` helper and `place.rs` both add `O_NONBLOCK` for this reason; add it here too.</comment>

<file context>
@@ -295,6 +301,43 @@ pub(super) fn copy_file_with_progress(
+    }
+    let file = std::fs::OpenOptions::new()
+        .read(true)
+        .custom_flags(libc::O_NOFOLLOW)
+        .open(local)
+        .with_context(|| format!("cannot read {}", local.display()))?;
</file context>
Suggested change
.custom_flags(libc::O_NOFOLLOW)
.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK)

Some(SERVICE.get_or_init(|| GitSeedService::new(fs.roots().clone())))
Some(SERVICE.get_or_init(|| {
#[cfg(debug_assertions)]
if let Some(dir) = std::env::var_os(TEST_GITHUB_VAR)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: A relative CMUX_TEST_GIT_SEED_GITHUB is ignored without any signal, so the seed quietly clones from real GitHub instead of the test directory. Warn or panic when the variable is set but not absolute, rather than filtering it out.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui-core/src/git_seed.rs, line 52:

<comment>A relative `CMUX_TEST_GIT_SEED_GITHUB` is ignored without any signal, so the seed quietly clones from real GitHub instead of the test directory. Warn or panic when the variable is set but not absolute, rather than filtering it out.</comment>

<file context>
@@ -35,12 +35,28 @@ pub const GIT_SEED_COMMANDS: [&str; 2] = ["git.seed.start", "git.seed.status"];
-    Some(SERVICE.get_or_init(|| GitSeedService::new(fs.roots().clone())))
+    Some(SERVICE.get_or_init(|| {
+        #[cfg(debug_assertions)]
+        if let Some(dir) = std::env::var_os(TEST_GITHUB_VAR)
+            .map(std::path::PathBuf::from)
+            .filter(|dir| dir.is_absolute())
</file context>

stack.push(entry.path());
} else {
let relative = entry.path().strip_prefix(root).unwrap().display().to_string();
found.insert(relative, std::fs::read_to_string(entry.path()).unwrap_or_default());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: files() turns any read failure into an empty string, so binary or non-UTF-8 files compare equal regardless of content. Read as bytes (std::fs::read) and compare Vec<u8>, or unwrap the read.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/tests/link_seed.rs, line 201:

<comment>`files()` turns any read failure into an empty string, so binary or non-UTF-8 files compare equal regardless of content. Read as bytes (`std::fs::read`) and compare `Vec<u8>`, or unwrap the read.</comment>

<file context>
@@ -0,0 +1,373 @@
+                stack.push(entry.path());
+            } else {
+                let relative = entry.path().strip_prefix(root).unwrap().display().to_string();
+                found.insert(relative, std::fs::read_to_string(entry.path()).unwrap_or_default());
+            }
+        }
</file context>

/// wire reaches it.
#[doc(hidden)]
#[must_use]
pub fn with_test_github(roots: Roots, github: &std::path::Path) -> Self {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: with_test_github disables the GitHub credential path and enables protocol.file, but it is compiled into release builds. Gate it with #[cfg(debug_assertions)] to match the debug-only seam in installed(), so release builds cannot construct this service.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui-core/src/git_seed/job.rs, line 193:

<comment>`with_test_github` disables the GitHub credential path and enables `protocol.file`, but it is compiled into release builds. Gate it with `#[cfg(debug_assertions)]` to match the debug-only seam in `installed()`, so release builds cannot construct this service.</comment>

<file context>
@@ -183,6 +183,27 @@ impl GitSeedService {
+    /// wire reaches it.
+    #[doc(hidden)]
+    #[must_use]
+    pub fn with_test_github(roots: Roots, github: &std::path::Path) -> Self {
+        let local = format!("file://{}/", github.display());
+        let config = Config {
</file context>
Suggested change
pub fn with_test_github(roots: Roots, github: &std::path::Path) -> Self {
#[cfg(debug_assertions)]
pub fn with_test_github(roots: Roots, github: &std::path::Path) -> Self {

since_last,
}),
("clone", [remote]) if !since_last => {
let repo = repo.filter(|repo| repo.split('/').count() == 2)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The --repo check accepts empty owner or name parts such as o/ and /n. Require both parts to be non-empty.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/src/link/seed_cli.rs, line 225:

<comment>The `--repo` check accepts empty owner or name parts such as `o/` and `/n`. Require both parts to be non-empty.</comment>

<file context>
@@ -0,0 +1,631 @@
+            since_last,
+        }),
+        ("clone", [remote]) if !since_last => {
+            let repo = repo.filter(|repo| repo.split('/').count() == 2)?;
+            Some(Command::Clone { socket: socket?, repo, git_ref, remote: absolute(remote)? })
+        }
</file context>
Suggested change
let repo = repo.filter(|repo| repo.split('/').count() == 2)?;
let repo = repo.filter(|repo| {
repo.split_once('/').is_some_and(|(owner, name)| {
!owner.is_empty() && !name.is_empty() && !name.contains('/')
})
})?;

.with_context(|| format!("cannot write {}", part.display()))?;
file.write_all(&serde_json::to_vec(record)?)?;
file.sync_all()?;
std::fs::rename(&part, &path).with_context(|| format!("cannot write {}", path.display()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The rename is not made durable because the parent folder is never synced. After a crash the seed record can revert to an older commit set or disappear, and --since-last then plans from stale state or reports seed.no_previous. Sync the parent folder after the rename, as agent_cli/record.rs does.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/src/link/seed_cli/record.rs, line 91:

<comment>The rename is not made durable because the parent folder is never synced. After a crash the seed record can revert to an older commit set or disappear, and `--since-last` then plans from stale state or reports `seed.no_previous`. Sync the parent folder after the rename, as `agent_cli/record.rs` does.</comment>

<file context>
@@ -0,0 +1,92 @@
+        .with_context(|| format!("cannot write {}", part.display()))?;
+    file.write_all(&serde_json::to_vec(record)?)?;
+    file.sync_all()?;
+    std::fs::rename(&part, &path).with_context(|| format!("cannot write {}", path.display()))
+}
</file context>

#[doc(hidden)]
#[must_use]
pub fn with_test_github(roots: Roots, github: &std::path::Path) -> Self {
let local = format!("file://{}/", github.display());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: A non-UTF-8 absolute directory accepted by the environment seam is lossy-converted by display(), so the rewritten file:// URL points elsewhere and seeding fails. Encode the OS path without loss or reject it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui-core/src/git_seed/job.rs, line 194:

<comment>A non-UTF-8 absolute directory accepted by the environment seam is lossy-converted by `display()`, so the rewritten `file://` URL points elsewhere and seeding fails. Encode the OS path without loss or reject it.</comment>

<file context>
@@ -183,6 +183,27 @@ impl GitSeedService {
+    #[doc(hidden)]
+    #[must_use]
+    pub fn with_test_github(roots: Roots, github: &std::path::Path) -> Self {
+        let local = format!("file://{}/", github.display());
+        let config = Config {
+            git: Git {
</file context>

@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
…art; LFS on clone; a machine that does not answer identify is an error

Review (gpt-6-astra) at aad041c: the bundle was world-readable under umask 022.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmux-tui/crates/cmux-tui/src/link/seed_cli.rs">

<violation number="1" location="cmux-tui/crates/cmux-tui/src/link/seed_cli.rs:245">
P2: An unanswered `identify` now fails `Machine::read` before any seed or copy attempt, so `push`, `push --since-last` and `clone` never reach the copy fallback. `machine_gate.rs` says an unanswered `identify` should leave the verb's own error, not a gate-specific failure. Either drop this check so an unanswered identify keeps the old `seeds` behaviour, or document the exception in `machine_gate.rs`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

/// Refuses a machine too old for `fs-v1` (`machine_outdated`).
fn read(socket: &'a Path) -> Result<Self, SeedError> {
let identity = machine_gate::identify(socket);
if identity.is_none() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: An unanswered identify now fails Machine::read before any seed or copy attempt, so push, push --since-last and clone never reach the copy fallback. machine_gate.rs says an unanswered identify should leave the verb's own error, not a gate-specific failure. Either drop this check so an unanswered identify keeps the old seeds behaviour, or document the exception in machine_gate.rs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/src/link/seed_cli.rs, line 245:

<comment>An unanswered `identify` now fails `Machine::read` before any seed or copy attempt, so `push`, `push --since-last` and `clone` never reach the copy fallback. `machine_gate.rs` says an unanswered `identify` should leave the verb's own error, not a gate-specific failure. Either drop this check so an unanswered identify keeps the old `seeds` behaviour, or document the exception in `machine_gate.rs`.</comment>

<file context>
@@ -242,6 +242,12 @@ impl<'a> Machine<'a> {
     /// Refuses a machine too old for `fs-v1` (`machine_outdated`).
     fn read(socket: &'a Path) -> Result<Self, SeedError> {
         let identity = machine_gate::identify(socket);
+        if identity.is_none() {
+            return Err(SeedError::new(
+                "sync_failed",
</file context>

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at 9aabf92

Reviewer: codex gpt-6-astra high, fast tier.

Follow-ups: 2. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward.

SHIP at 9aabf92

Visual verdict: no images; Leo tries the build

  1. [P1][follow-up] seed_cli.rs:358-380 snapshots whether the remote folder exists, then recursively deletes it after a fallback if the snapshot said “absent.” A concurrent creator can have populated that path while planning/seeding, causing another push’s data to be deleted.

  2. [P2][follow-up] seed_cli.rs:486-489 ignores failures deleting the uploaded bundle and its part file. If the machine disconnects after accepting the job, the bundle remains in the machine’s filesystem indefinitely while the CLI reports success.

Follow-ups:

  • [P1][follow-up] Make failed-seed cleanup ownership-aware before deleting the remote folder.
  • [P2][follow-up] Retry or surface upload cleanup failures so stale bundles cannot be silently retained.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
@teamleaderleo
teamleaderleo merged commit 9f5e2e1 into feat-cmux-next Nov 1, 2026
113 of 121 checks passed
@teamleaderleo
teamleaderleo deleted the seed-push branch November 1, 2026 09:50
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
teamleaderleo added a commit that referenced this pull request Nov 1, 2026
lawrencecchen added a commit that referenced this pull request Nov 1, 2026
…ads use get

The ratchet at the merge with feat-cmux-next e27f116 (18cef5a,
job 9d666b02dcb4c69dbfc147f0) is still red: cmux-tui indexing_slicing
849 > 831. The 18 new hits after #30414 come from:
- d8e1b7c (#29234) cli/terminal_talk/send.rs stage_failure: 2
- 9f5e2e1 (#30634) link/seed_cli.rs: 13, link/mod.rs `&args[1..]`: 1
- 7b30784 (#30664) link/machine_pin.rs: 1, link/mod.rs `&args[2..]`: 1

Each is now a get / pointer-free get read, `JsonSet::set` for the one
write, and `args.get(n..)` for the slices (the branch runs only when
`args.get(n - 1)` matched, so the slice is always there). A missing key
read through get gives None where indexing gave Null: `as_str()` and
`as_bool()` see the same, and json! writes None as null.
No behavior change; the baseline is not raised.
@lawrencecchen

Copy link
Copy Markdown
Contributor

This PR added new clippy::indexing_slicing hits to cmux-tui production code: Value v["key"] reads or writes, or &args[n..]. Together with #30414, #29234 and #30634 / #30664 make the panic ratchet red at feat tip e27f116: cmux-tui indexing_slicing 849 > 831 (job 9d666b02dcb4c69dbfc147f0). #30661 fixes them forward with get/JsonSet::set/args.get(n..) and does not change behavior. Before you land, run cmux-ci run --class light --script scripts/ci/cmux-tui-rust-check.sh --arg panic --os linux --ref SHA.

lawrencecchen added a commit that referenced this pull request Nov 1, 2026
…0414, #29234, #30634, #30664 and #30574 use checked forms (#30661)

* base red: panic ratchet, #30414's new index and string slices in start_chat_cli use checked forms

The Rust panic ratchet is red on feat-cmux-next at 7e0a0f2 (tip
watcher job 21c6ba0ebda787821cc7bc4b): cmux-tui indexing_slicing
833 > 831 and string_slice 84 > 82. All four hits are from 0507fc4
(#30414) in cmux-tui/src/link/start_chat_cli.rs:
- valid_environment: `id[4..]` -> `id.get(4..)`; the id already starts
  with the ASCII "env_", so the slice is always Some and the same.
- github_repo: `&url[scheme.len()..]` -> `split_at_checked`, None in
  exactly the cases where the `get(..scheme.len())` before it was None.
- Ensure::body: `body["ref"] = ..`, `body["environment"] = ..` ->
  `JsonSet::set` on the same object literal (same insert).
No behavior change; the baseline is not raised.

* base red: panic ratchet, #29234's, #30634's and #30664's new index reads use get

The ratchet at the merge with feat-cmux-next e27f116 (18cef5a,
job 9d666b02dcb4c69dbfc147f0) is still red: cmux-tui indexing_slicing
849 > 831. The 18 new hits after #30414 come from:
- d8e1b7c (#29234) cli/terminal_talk/send.rs stage_failure: 2
- 9f5e2e1 (#30634) link/seed_cli.rs: 13, link/mod.rs `&args[1..]`: 1
- 7b30784 (#30664) link/machine_pin.rs: 1, link/mod.rs `&args[2..]`: 1

Each is now a get / pointer-free get read, `JsonSet::set` for the one
write, and `args.get(n..)` for the slices (the branch runs only when
`args.get(n - 1)` matched, so the slice is always there). A missing key
read through get gives None where indexing gave Null: `as_str()` and
`as_bool()` see the same, and json! writes None as null.
No behavior change; the baseline is not raised.

* base red: panic ratchet, #30574's new index reads in start_chat_cli use get

#30574 (93772c4) landed after the e27f116 merge and added four
Value index reads in start-chat plan (line["machine"], ["created"],
["snapshot"], ["environment"]). They are now get reads: a missing key
is None where indexing gave Null, as_str() sees the same and json!
writes None as null. No behavior change.
@blacksmith-sh

blacksmith-sh Bot commented Nov 1, 2026

Copy link
Copy Markdown

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
nightly must build and hand off the macOS 26 helper and Xcode 26.5 app before publishin
g/
nightly must build and hand off the macOS 26 helper and Xcode 26.5 app before publishin
g
View Logs
test_each_app_reads_the_feed_its_publisher_writes: AssertionError()/
test_each_app_reads_the_feed_its_publisher_writes: AssertionError()
View Logs
test_helper_never_takes_a_hosted_mac_and_skips_compile_on_hit (main.WorkflowTests.t
est_helper_never_takes_a_hosted_mac_and_skips_compile_on_hit)/
test_helper_never_takes_a_hosted_mac_and_skips_compile_on_hit (main.WorkflowTests.t
est_helper_never_takes_a_hosted_mac_and_skips_compile_on_hit)
View Logs
test_runtime_added_app_capabilities_are_runtime_only (main.StaticCapabilityRuntimeO
nlyTests.test_runtime_added_app_capabilities_are_runtime_only)/
test_runtime_added_app_capabilities_are_runtime_only (main.StaticCapabilityRuntimeO
nlyTests.test_runtime_added_app_capabilities_are_runtime_only)
View Logs
test_the_nightly_is_covered (main.NightlyStepCoverageTests.test_the_nightly_is_cove
red)/
test_the_nightly_is_covered (main.NightlyStepCoverageTests.test_the_nightly_is_cove
red)
View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs
test_under_githubs_limit_with_headroom (main.NightlyWorkflowSize.test_under_githubs
_limit_with_headroom)/
test_under_githubs_limit_with_headroom (main.NightlyWorkflowSize.test_under_githubs
_limit_with_headroom)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 9aabf929 Deployed Nov 1, 2026 by teamleaderleo via cmux-tui tree preflight #95312
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants