8.4 KiB
8.4 KiB
spec, issue, created, status
| spec | issue | created | status |
|---|---|---|---|
| docs/specs/executors/requirements/ssh-executor.md | https://github.com/kdlbs/kandev/issues/2413 | 2026-08-08 | implemented |
Implementation Plan: SSH Workspace Preparation
Overview
Issue #2413 is caused by a split launch pipeline: SSHPreparer.Prepare reports a successful
validation-only step, while SSHExecutor.CreateInstance creates the remote task directory and starts
agentctl without resolving or executing the profile prepare script. The only current root checkout
is the special RemoteContributions[""] path, so an ordinary provider-backed repository reaches the
agent as an empty directory. The repair gives SSH the same prepare/cleanup contract as Sprites while
preserving SSH host-key, credential-forwarding, and resumable-workspace boundaries.
Backend
SSH default and script resolution
- Add an idempotent
sshcase toapps/backend/internal/agent/runtime/lifecycle/default_scripts.go. It must initialize the primary checkout at the remote task workspace root, verify a reused checkout's credential-freeorigin, run{{repository.setup_script}}, and leave feature-branch selection to the unconditionalKandevBranchCheckoutPostlude. - Add SSH prepare/cleanup resolution beside the executor (prefer a focused
executor_ssh_scripts.gorather than growingexecutor_ssh.go). Reusescriptengineproviders for workspace, repository, worktree, Git identity, GitHub auth, and remote contribution bindings. Map agentctl install/start placeholders to the explicit SSH lifecycle so a profile script cannot start an unauthenticated duplicate controller. - Execute the resolved prepare script through the selected remote login shell with the effective profile/repository environment explicitly configured for that executor. Keep secret values on stdin, bound execution with a timeout, bound retained output, and return a generic checkout error where Git stderr could contain credentials. Preserve the narrower existing allowlist for the agent subprocess; prepare-script access must not accidentally broaden long-lived agent environment.
Launch ordering and postcondition
- Refactor
SSHExecutor.CreateInstanceinapps/backend/internal/agent/runtime/lifecycle/executor_ssh.gointo the observable order: connect and verify host; upload/resolveagentctl; create the task workspace; upload credentials; execute preparation; verify that an attached primary repository is a matching Git checkout; create the session runtime directory; preflight the selected agent binary; then launch/forwardagentctlfrom the remote workspace. - Remove the post-controller ordinary/special-case gap by routing contribution setup through the same
resolved prepare pipeline. Preserve exact-head validation and source-upstream behavior from
executor_ssh_remote_contribution.go; consolidate or delete that SSH-only script only after its existing security and resume tests are represented in the shared path. - A failed or cancelled preparation must close the SSH client and leave no remote controller, port forward, or tracked session. A reused checkout must preserve local descendants and untracked files.
Terminal cleanup
- Resolve and run
MetadataKeyCleanupScriptover the tracked SSH connection inStopInstanceonly whenshouldRunExecutorCleanup(instance.StopReason)is true. Run it againstMetadataKeySSHRemoteTaskDirwith the same non-secret providers as preparation, then stop the remote controller and close forwarding/SSH even if cleanup fails. - Keep any cleanup environment needed after launch only in the in-memory
sshSessionState; rebuild it from the resume request after backend recovery and never persist resolved secret values. - Ordinary user Stop and
StopReasonBackendShutdownmust not run cleanup. Preserve the remote task directory under all current SSH stop paths; this repair does not add automatic remote deletion.
Tests
- What:
DefaultPrepareScript("ssh")is populated, contains the repository setup placeholder, has no inline task-branch checkout, and safely reuses a matching workspace. File:apps/backend/internal/agent/runtime/lifecycle/default_scripts_test.goand focused SSH script tests. How: execute the resolved script against temporary bare repositories; assert root checkout, credential-freeorigin, task branch, setup marker, preserved local descendant, and conflicting origin rejection. - What: SSH preparation resolves supported placeholders, receives only the approved environment,
runs before controller launch, and fails closed on non-zero exit, timeout, absent checkout, or
cancellation.
File: focused tests beside
executor_ssh_scripts.goplus narrow executor orchestration tests. How: fake command/session seams record ordering and output without dialing a user host. - What: terminal cleanup runs for archive/delete reasons, skips Stop/backend shutdown, resolves
the remote workspace, and never prevents controller teardown after failure.
File: focused SSH lifecycle tests beside
executor_ssh.go. How: fake SSH command and controller-stop seams assert invocation order and error handling. - What: contribution checkouts keep target
origin, exact source SHA, source upstream, and local descendant reuse after consolidation. File: existing/focused tests inapps/backend/internal/agent/runtime/lifecycle/executor_ssh_connection_test.goor the new script test file. How: temporary target/source bare repositories with URL rewrites.
E2E Tests
- Scenario: GIVEN a provider-backed repository and the default SSH profile, WHEN a task starts,
THEN the remote task directory is a Git checkout on the task branch, contains the repository file,
and is the workspace used by
agentctland the agent. File:apps/web/e2e/tests/ssh/launch-task.spec.tsor a focused sibling SSH workspace spec. What to verify: real SSH transport, remotegit rev-parse --show-toplevel, repository content, session metadata, and controller log location. - Scenario: GIVEN custom prepare and cleanup scripts, WHEN the task starts and is later archived or deleted, THEN the prepare marker exists before the agent completes and the cleanup marker appears only during terminal teardown. File: the same focused SSH workspace spec. What to verify: profile persistence, placeholder resolution, preparation failure gating, cleanup reason gating, and retained remote workspace evidence.
- Extend
apps/web/e2e/helpers/http-git-server.tsonly as needed to expose a disposable provider URL reachable from the SSH container. Keep the target isolated, credential-free, and torn down by the owning fixture.
Public Documentation
- Update
docs/public/executors.mdto describe automatic primary checkout, prepare/cleanup script execution, failure behavior, and retained workspace cleanup semantics. - Update the SSH row in
docs/public/feature-status.mdso it no longer claims repository and script preparation are unsupported. - Treat
docs/public/executors.mdas a how-to/reference hybrid whose dominant type is how-to;docs/public/feature-status.mdremains reference.
Verification Results
- Task 01 targeted SSH lifecycle tests:
rtk go test ./internal/agent/runtime/lifecycle -run 'Test.*SSH.*(Prepare|Workspace|Cleanup|Contribution|Origin|Checkout)' -count=1— 23 passed. - Task 01 full lifecycle package:
rtk go test ./internal/agent/runtime/lifecycle -count=1— 1,141 passed. - Task 01 race-focused run:
rtk go test -race ./internal/agent/runtime/lifecycle -run 'TestSSH|TestShouldRunExecutorCleanupIncludesCascadeTerminalReasons' -count=1— 47 passed. - Task 02 container-backed SSH regression:
rtk env KANDEV_E2E_CONTAINERS=1 rtk pnpm e2e:run --no-build --project containers tests/ssh/launch-task.spec.ts— 9 passed. - Task 03 public docs tests:
rtk node --test scripts/validate-public-docs.test.mjs— 58 passed;rtk node scripts/validate-public-docs.mjs— 41 pages validated.
Implementation Waves And Parallel Candidates
Wave 1:
Wave 2:
Wave 3:
Execution is sequential in the primary conversation. The tasks share behavior contracts and are not marked parallel-safe.