Back to feed

sledtools/pika branch #87

pika-orch-incus-cleanup-18

Derive Incus staged payload roots from job paths

Target branch: master

Merge Commit: 9782dc0a7b68a12a8dcf43c94d6d35613b51637c

branch: merged tutorial: ready ci: success
Open CI Details

Continuous Integration

CI: success

Compact status on the review page, with full logs on the CI page.

Open CI Details

Latest run #113 success

6 passed

head 3244526281e15060bef5f3382852cba28d8d6b80 · queued 2026-03-26 01:04:11 · 6 lane(s)

queued 13s · ran 29s

check-notifications · success check-agent-contracts · success check-pikachat · success check-pikachat-typescript · success check-pikachat-openclaw-e2e · success check-fixture · success

Summary

This branch removes the hardcoded remote_workspace_deps_dir and remote_workspace_build_dir fields from RemoteLinuxVmSharedContext and replaces them with a dynamic derivation strategy. Instead of storing pre-computed remote staged payload paths in the shared context, the new code introduces a remote_job_path helper that translates local job-relative paths to their remote equivalents by stripping the local job directory prefix and re-rooting under the remote job directory. A new staged_payload_remote_dirs function uses this helper to compute the set of staged payload directories on demand from HostContext. The staged_payload_source_root function in the Incus executor is updated to accept local_job_dir and remote_job_dir instead of the former remote_output_root, making both the localhost and remote-host code paths derive their paths consistently. The ensure_remote_linux_vm_directories function switches from a static format string to a dynamically built directory list, and all affected call sites and tests are updated accordingly.

Tutorial Steps

Remove hardcoded remote staged payload fields from shared context

Intent: Eliminate the `remote_workspace_deps_dir` and `remote_workspace_build_dir` fields from `RemoteLinuxVmSharedContext` so that staged payload paths are no longer duplicated between the host context and the remote context. This reduces the surface area for path inconsistencies.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -68,8 +68,6 @@ struct RemoteLinuxVmSharedContext {
     remote_artifacts_dir: PathBuf,
     remote_cargo_home_dir: PathBuf,
     remote_target_dir: PathBuf,
-    remote_workspace_deps_dir: PathBuf,
-    remote_workspace_build_dir: PathBuf,
 }
@@ -1777,12 +1775,6 @@ fn remote_linux_vm_context(
         remote_artifacts_dir: remote_job_dir.join("artifacts"),
         remote_cargo_home_dir: remote_work_dir.join("cache").join("cargo-home"),
         remote_target_dir: remote_work_dir.join("cache").join("cargo-target"),
-        remote_workspace_deps_dir: remote_job_dir
-            .join("staged-linux-rust")
-            .join("workspace-deps"),
-        remote_workspace_build_dir: remote_job_dir
-            .join("staged-linux-rust")
-            .join("workspace-build"),
     };

Two fields are deleted from the RemoteLinuxVmSharedContext struct:

  • remote_workspace_deps_dir
  • remote_workspace_build_dir

These were previously constructed by joining staged-linux-rust/workspace-deps and staged-linux-rust/workspace-build onto the remote_job_dir. The construction site in remote_linux_vm_context is cleaned up to no longer produce them.

This is the foundational change that motivates everything else in the branch: the remote paths for staged payloads will now be derived at the point of use rather than stored ahead of time.

Introduce `remote_job_path` helper for local-to-remote path translation

Intent: Provide a single reusable function that translates any local path under the local job directory into its corresponding path under the remote job directory. This is the core derivation mechanism that replaces the removed struct fields.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -1894,7 +1886,37 @@ fn run_ssh_command(remote_host: &str, command: &str) -> Command {
+fn remote_job_path(
+    local_job_dir: &Path,
+    remote_job_dir: &Path,
+    local_path: &Path,
+) -> anyhow::Result<PathBuf> {
+    let relative = local_path.strip_prefix(local_job_dir).with_context(|| {
+        format!(
+            "derive remote job path for {} under {}",
+            local_path.display(),
+            local_job_dir.display()
+        )
+    })?;
+    Ok(remote_job_dir.join(relative))
+}

The new remote_job_path function accepts three arguments:

  1. local_job_dir — the root of the job on the host.
  2. remote_job_dir — the root of the job on the remote machine.
  3. local_path — the specific local path to translate.

It calls strip_prefix to obtain the relative portion of local_path under local_job_dir, then joins that relative portion onto remote_job_dir. If the local path is not actually beneath the local job directory, the function returns a contextual error.

This replaces all prior assumptions about the exact subdirectory layout of staged payloads — whatever structure the local job directory has, the remote side mirrors it faithfully.

Introduce `staged_payload_remote_dirs` to dynamically collect remote directories

Intent: Replace the static references to removed struct fields with a function that derives the set of staged payload remote directories from `HostContext` using the new `remote_job_path` helper.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -1894,7 +1886,37 @@
+fn staged_payload_remote_dirs(
+    ctx: &HostContext,
+    shared: &RemoteLinuxVmSharedContext,
+) -> anyhow::Result<Vec<PathBuf>> {
+    [
+        ctx.staged_linux_rust_workspace_deps_dir.as_ref(),
+        ctx.staged_linux_rust_workspace_build_dir.as_ref(),
+    ]
+    .into_iter()
+    .flatten()
+    .map(|path| remote_job_path(&ctx.job_dir, &shared.remote_job_dir, path))
+    .collect()
+}

staged_payload_remote_dirs inspects the two Option<PathBuf> fields on HostContext:

  • staged_linux_rust_workspace_deps_dir
  • staged_linux_rust_workspace_build_dir

For each Some value it calls remote_job_path to translate the local path to the remote equivalent. None values are silently skipped via .flatten(). The result is a Vec<PathBuf> of zero, one, or two directories.

This design is resilient to jobs that don't use staged Linux Rust payloads — the returned vector will simply be empty.

Refactor `ensure_remote_linux_vm_directories` to use dynamic directory list

Intent: Replace the static eight-argument `format!` call with a dynamically built directory vector that includes the staged payload dirs only when they exist, and thread `HostContext` through so `staged_payload_remote_dirs` can be called.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -1904,17 +1926,21 @@ fn ensure_remote_linux_vm_directories(
-    let command = format!(
-        "set -euo pipefail; mkdir -p {} {} {} {} {} {} {} {}",
-        shell_single_quote(&shared.remote_job_dir.display().to_string()),
-        shell_single_quote(&remote_snapshot_parent.display().to_string()),
-        shell_single_quote(&shared.remote_job_dir.join("vm").display().to_string()),
-        shell_single_quote(&shared.remote_artifacts_dir.display().to_string()),
-        shell_single_quote(&shared.remote_cargo_home_dir.display().to_string()),
-        shell_single_quote(&shared.remote_target_dir.display().to_string()),
-        shell_single_quote(&shared.remote_workspace_deps_dir.display().to_string()),
-        shell_single_quote(&shared.remote_workspace_build_dir.display().to_string()),
-    );
+    let mut dirs = vec![
+        shared.remote_job_dir.clone(),
+        remote_snapshot_parent.to_path_buf(),
+        shared.remote_job_dir.join("vm"),
+        shared.remote_artifacts_dir.clone(),
+        shared.remote_cargo_home_dir.clone(),
+        shared.remote_target_dir.clone(),
+    ];
+    dirs.extend(staged_payload_remote_dirs(ctx, shared)?);
+    let quoted_dirs = dirs
+        .iter()
+        .map(|path| shell_single_quote(&path.display().to_string()))
+        .collect::<Vec<_>>()
+        .join(" ");
+    let command = format!("set -euo pipefail; mkdir -p {quoted_dirs}");

The function signature gains a new ctx: &HostContext parameter. Internally, the six always-present directories are collected into a Vec, and staged_payload_remote_dirs appends zero or more additional entries.

All directory paths are then shell-quoted and joined into a single mkdir -p command. This replaces the previous approach of individually interpolating eight shell_single_quote calls into a fixed format string.

Benefits:

  • The mkdir -p command only includes staged payload directories when the job actually declares them.
  • Adding a new directory in the future requires only adding it to the dirs vector or to staged_payload_remote_dirs.

Update call sites to pass `HostContext` to directory setup

Intent: Propagate the new `ctx` parameter to the two call sites of `ensure_remote_linux_vm_directories`.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -961,7 +959,7 @@ fn run_remote_linux_vm_job(job: &JobSpec, ctx: &HostContext) -> anyhow::Result<J
         phases.record(RemoteLinuxVmPhase::PrepareDirectories, || {
-            ensure_remote_linux_vm_directories(&shared, &ctx.host_log_path)
+            ensure_remote_linux_vm_directories(ctx, &shared, &ctx.host_log_path)
         })?;
@@ -1044,7 +1042,7 @@ pub(crate) fn prepare_remote_linux_vm_backend(
-    ensure_remote_linux_vm_directories(&shared, log_path)?;
+    ensure_remote_linux_vm_directories(ctx, &shared, log_path)?;

Both run_remote_linux_vm_job and prepare_remote_linux_vm_backend already have ctx: &HostContext in scope. The change is a straightforward addition of ctx as the first argument to ensure_remote_linux_vm_directories.

Refactor Incus `staged_payload_source_root` to derive paths from job dirs

Intent: Change the Incus staged payload resolution to accept local and remote job directory roots instead of the pre-computed remote output root, so that the remote path for non-localhost hosts is derived dynamically using `remote_job_path`.

Affected files: crates/pikaci/src/executor/incus.rs

Evidence
@@ -802,14 +802,15 @@ fn add_declared_payload_mount(
 fn staged_payload_source_root(
     local_mount_path: Option<&PathBuf>,
-    remote_output_root: &Path,
+    local_job_dir: &Path,
+    remote_job_dir: &Path,
     remote_host: &str,
 ) -> anyhow::Result<PathBuf> {
@@ -819,16 +820,23 @@
-    Ok(remote_output_root.to_path_buf())
+    let local_mount_path = local_mount_path.ok_or_else(|| {
+        anyhow!(
+            "missing local staged payload mount path for remote job {}",
+            local_job_dir.display()
+        )
+    })?;
+    remote_job_path(local_job_dir, remote_job_dir, local_mount_path)

Previously the non-localhost code path simply returned the remote_output_root as-is — this was the value taken from the now-removed struct fields. The new implementation:

  1. Requires local_mount_path to be Some for both localhost and remote cases.
  2. For localhost: canonicalizes the local path (unchanged behavior).
  3. For remote hosts: calls remote_job_path to translate the local mount path to the remote equivalent.

This means the Incus module no longer needs any knowledge of the staged-linux-rust/workspace-deps or staged-linux-rust/workspace-build naming convention — it simply mirrors whatever local structure exists.

Update Incus `configure_remote_incus_devices` to pass job dirs

Intent: Adjust the call sites within Incus device configuration to pass `ctx.job_dir` and `remote.shared.remote_job_dir` instead of the removed shared context fields.

Affected files: crates/pikaci/src/executor/incus.rs

Evidence
@@ -845,13 +853,15 @@
         let workspace_deps_root = staged_payload_source_root(
             ctx.staged_linux_rust_workspace_deps_dir.as_ref(),
-            &remote.shared.remote_workspace_deps_dir,
+            &ctx.job_dir,
+            &remote.shared.remote_job_dir,
             &remote.shared.remote_host,
         )?;
         let workspace_build_root = staged_payload_source_root(
             ctx.staged_linux_rust_workspace_build_dir.as_ref(),
-            &remote.shared.remote_workspace_build_dir,
+            &ctx.job_dir,
+            &remote.shared.remote_job_dir,
             &remote.shared.remote_host,
         )?;

Both calls to staged_payload_source_root inside configure_remote_incus_devices are updated:

  • The second argument changes from &remote.shared.remote_workspace_deps_dir / &remote.shared.remote_workspace_build_dir to &ctx.job_dir.
  • A new third argument &remote.shared.remote_job_dir is added.

The function now derives the full remote path internally, so the caller only needs to supply the two directory roots.

Update tests to match new signatures and derivation logic

Intent: Align all test helpers and assertions with the refactored function signatures, removing references to deleted struct fields and verifying the dynamic path derivation.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -2465,14 +2492,6 @@
             remote_cargo_home_dir: Path::new("/var/tmp/pikaci/cache/cargo-home").to_path_buf(),
             remote_target_dir: Path::new("/var/tmp/pikaci/cache/cargo-target").to_path_buf(),
-            remote_workspace_deps_dir: Path::new(
-                "/var/tmp/pikaci/runs/run/jobs/job/staged-linux-rust/workspace-deps",
-            )
-            .to_path_buf(),
-            remote_workspace_build_dir: Path::new(
-                "/var/tmp/pikaci/runs/run/jobs/job/staged-linux-rust/workspace-build",
-            )
-            .to_path_buf(),
@@ -3603,6 +3622,25 @@
+        let ctx = HostContext {
+            source_root: PathBuf::from("/tmp/source"),
+            workspace_snapshot_dir: PathBuf::from("/tmp/snapshot"),
+            ...
+            staged_linux_rust_workspace_deps_dir: Some(PathBuf::from(
+                "/tmp/run/jobs/job/staged-linux-rust/workspace-deps",
+            )),
+            staged_linux_rust_workspace_build_dir: Some(PathBuf::from(
+                "/tmp/run/jobs/job/staged-linux-rust/workspace-build",
+            )),
+        };
@@ -3654,11 +3688,13 @@
-        let local_mount = root.join("workspace-build");
+        let local_job_dir = root.join("job");
+        let local_mount = local_job_dir.join("staged-linux-rust/workspace-build");
         let source = incus::resolve_staged_payload_source_root_for_test(
             Some(&local_mount),
-            Path::new("/var/tmp/pikaci-prepared-output/runs/run/jobs/job/staged-linux-rust/workspace-build"),
+            &local_job_dir,
+            Path::new("/var/tmp/pikaci-prepared-output/runs/run/jobs/job"),
             "localhost",
         )
@@ -3671,12 +3707,19 @@
+        let local_job_dir = Path::new("/tmp/run/jobs/job");
+        let local_mount = PathBuf::from("/tmp/run/jobs/job/staged-linux-rust/workspace-build");
+        let remote_job_dir = Path::new("/var/tmp/pikaci-prepared-output/runs/run/jobs/job");
         let source = incus::resolve_staged_payload_source_root_for_test(
-            None, remote_root, "pika-build"
+            Some(&local_mount),
+            local_job_dir,
+            remote_job_dir,
+            "pika-build",
         )

Several test updates reinforce the new design:

  1. Test helper RemoteLinuxVmSharedContext construction — the two deleted fields are removed from the test fixture builder.

  2. ensure_remote_linux_vm_directories_create_declared_staged_output_dirs — a full HostContext is constructed with staged_linux_rust_workspace_deps_dir and staged_linux_rust_workspace_build_dir set. The test now builds the expected mkdir -p command using staged_payload_remote_dirs and the dynamic dirs vector, exactly mirroring the production code path.

  3. Localhost staged payload test — the local mount is restructured to sit under local_job_dir.join("staged-linux-rust/workspace-build") so that strip_prefix works correctly. The test helper call now passes local_job_dir and the remote job dir root instead of the full remote output path.

  4. Remote host staged payload test — previously passed None for local_mount_path, reflecting the old code path that simply returned the remote root. Now passes Some(&local_mount) since the remote path derivation requires the local mount to compute the relative suffix. The assertion confirms the derived remote path matches the expected full path.

The staged_payload_remote_dirs function is also added to the test module's import list, confirming it is pub(crate) visible for test reuse.

Diff