fix(ssh-executor): forward agent creds, fix terminal host config, restore linux/arm64#1559
fix(ssh-executor): forward agent creds, fix terminal host config, restore linux/arm64#1559iamcobolt wants to merge 12 commits into
Conversation
Follow-ups to kdlbs#1534 (remote macOS SSH hosts): - Restore linux/arm64 SSH-host support that existed before this change (Graviton/ARM Linux remotes). Re-adds the platform-gate case, the build-agentctl-linux-arm64 Make target, the release build/copy steps, the runtime-bundle required-helper entry, and the desktop runtime validators (Rust/shell/mjs). - Re-add ad-hoc signing of the darwin agentctl helpers in `make` (codesign on macOS, rcodesign on Linux/CI; warn if absent). Go already signs darwin/arm64 at link time; this also signs darwin/amd64 and re-signs uniformly, keeping the chain notarization-ready. - Enforce the darwin/arm64 helper carries a code signature at bundle-validation time (LC_CODE_SIGNATURE scan). An unsigned darwin/arm64 binary is SIGKILLed by Apple Silicon (AMFI) on launch, so catch it at packaging instead of on every remote launch. darwin/amd64 is intentionally exempt (Intel does not enforce; Go does not sign it). Verified: go build ./..., go test on lifecycle/launcher/ssh/metrics, release-desktop.test.sh, and a live SSH run to a real Apple Silicon Mac (agentctl uploaded, launched, "HTTP server bound successfully"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Env-authenticated agents (claude-acp reads CLAUDE_CODE_OAUTH_TOKEN, not a credentials file) failed on every SSH remote with "Authentication required". The SSH executor's CreateInstanceRequest never set Env, so agentctl's CollectAgentEnv(req.Env) had nothing to merge — unlike Docker (Env: req.Env). Forward a credential allowlist (CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI/GOOGLE_API_KEY, GITHUB_TOKEN, GH_TOKEN) into the remote agent instance, sourced from req.Env first then the control-plane env — never HOME/PATH/etc., which would break a different remote. Note: a complete fix should also run credential resolution for SSH (currently gated to containerized executors via isContainerizedExecutor) so a profile-configured token populates req.Env directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-restore Opening a terminal or restoring a finished SSH task failed with "host (or host_alias) is required in executor config". GetWorkspaceInfoForSession copied only ExecutorType from the executor record and dropped exec.Config (ssh_host, fingerprint, user); those keys only survived via a live ExecutorRunning record, so terminal-state sessions / post-restart / post-cleanup had no host config. Project the executor record's persistent SSH config into the workspace metadata as a fallback (non-overwriting, so a live running record still wins). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds Changeslinux/arm64 agentctl support and packaging
Runtime validation and SSH flow
Workspace fallback metadata
Estimated code review effort: 4 (Complex) | ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| apps/backend/internal/launcher/bundle.go | Adds MustBeSigned field and machoHasCodeSignature parser for bundle validation but io is not imported despite calling io.ReadFull causing a compile error; the io.ErrUnexpectedEOF handling logic is also wrong making all macho-signature tests fail. |
| apps/backend/internal/launcher/bundle_test.go | Tests for signature validation and machoHasCodeSignature are correctly written; they fail at runtime due to the io.ErrUnexpectedEOF handling bug in the implementation. |
| apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go | Adds sshRemoteAgentEnv which forwards only a credential allowlist from req.Env and enables linux/arm64 in requireSupportedRemotePlatform; logic is sound. |
| apps/backend/internal/agent/runtime/lifecycle/executor_backend.go | Introduces sshWorkspaceFallbackKeys and FilterSSHWorkspaceFallbackConfig including MetadataKeySSHHostAlias so alias-only executors survive workspace restore. |
| apps/backend/internal/task/service/service_turns.go | Adds mergeExecutorConfigMetadata to project stable SSH connection config into workspace metadata as a non-overwriting fallback when no live ExecutorRunning record exists. |
| apps/backend/internal/orchestrator/executor/executor_execute.go | Adds executorNeedsResolvedCredentials that gates applyContainerCredentials on SSH in addition to containerized executors. |
| .github/workflows/release.yml | Adds linux/arm64 agentctl cross-build step and copies it alongside the other helpers in the release artifact. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Orch as Orchestrator
participant Exec as executor_execute.go
participant SSH as executor_ssh_operations.go
participant Remote as Remote agentctl
Orch->>Exec: buildLaunchAgentRequest SSH executor
Note over Exec: executorNeedsResolvedCredentials ssh true
Exec->>Exec: applyContainerCredentials populates req.Env
Exec->>SSH: CreateInstance req
SSH->>SSH: sshRemoteAgentEnv filters credential allowlist from req.Env
SSH->>Remote: CreateInstanceRequest with Env credentials
Remote->>Remote: agentctl reads CLAUDE_CODE_OAUTH_TOKEN
participant WS as GetWorkspaceInfoForSession
participant DB as Executor record DB
WS->>DB: GetExecutorByID
DB-->>WS: exec Type SSH Config ssh_host
Note over WS: exec.Type == SSH
WS->>WS: mergeExecutorConfigMetadata via FilterSSHWorkspaceFallbackConfig
WS-->>Orch: WorkspaceInfo with ssh_host fallback
participant BV as validateRuntimeBundle
participant MO as machoHasCodeSignature
BV->>BV: check agentctl-darwin-arm64 MustBeSigned true
BV->>MO: machoHasCodeSignature path
MO->>MO: io.ReadFull parse Mach-O scan LC_CODE_SIGNATURE
MO-->>BV: signed ok
alt not ok
BV-->>Orch: error not parsable Mach-O
else not signed
BV-->>Orch: error not code-signed
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Orch as Orchestrator
participant Exec as executor_execute.go
participant SSH as executor_ssh_operations.go
participant Remote as Remote agentctl
Orch->>Exec: buildLaunchAgentRequest SSH executor
Note over Exec: executorNeedsResolvedCredentials ssh true
Exec->>Exec: applyContainerCredentials populates req.Env
Exec->>SSH: CreateInstance req
SSH->>SSH: sshRemoteAgentEnv filters credential allowlist from req.Env
SSH->>Remote: CreateInstanceRequest with Env credentials
Remote->>Remote: agentctl reads CLAUDE_CODE_OAUTH_TOKEN
participant WS as GetWorkspaceInfoForSession
participant DB as Executor record DB
WS->>DB: GetExecutorByID
DB-->>WS: exec Type SSH Config ssh_host
Note over WS: exec.Type == SSH
WS->>WS: mergeExecutorConfigMetadata via FilterSSHWorkspaceFallbackConfig
WS-->>Orch: WorkspaceInfo with ssh_host fallback
participant BV as validateRuntimeBundle
participant MO as machoHasCodeSignature
BV->>BV: check agentctl-darwin-arm64 MustBeSigned true
BV->>MO: machoHasCodeSignature path
MO->>MO: io.ReadFull parse Mach-O scan LC_CODE_SIGNATURE
MO-->>BV: signed ok
alt not ok
BV-->>Orch: error not parsable Mach-O
else not signed
BV-->>Orch: error not code-signed
end
Comments Outside Diff (1)
-
apps/backend/internal/launcher/bundle.go, line 3-8 (link)Missing
"io"import —io.ReadFullis called inmachoHasCodeSignaturebut theiopackage is not in the import block, sogo build ./apps/backend/internal/launcherfails to compile. This blocks every code path that touchesvalidateRuntimeBundle, including the bundle-startup check.
Reviews (8): Last reviewed commit: "Merge branch 'main' into fix/ssh-darwin-..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go`:
- Around line 647-649: The environment resolution in the SSH executor is
incorrectly falling back to the backend process environment when req.Env omits a
key, which can leak control-plane credentials to remote hosts. Update the
env-building logic in executor_ssh_operations.go around the val == "" branch so
it only forwards values explicitly present in req.Env, or add an explicit opt-in
path before calling os.Getenv(key); keep the change localized to the SSH env
propagation flow and verify the behavior in the executor path that assembles
remote environment variables.
In `@apps/backend/internal/launcher/bundle.go`:
- Around line 57-63: The bundle validation in runtimeBundle should fail closed
when machoHasCodeSignature cannot parse the helper artifact. Update the
MustBeSigned check so that runtimeBundle treats ok=false from
machoHasCodeSignature(path) as an error path, alongside the existing unsigned
case, and return a clear fmt.Errorf mentioning helper.Label and path. Keep the
logic in bundle.go around helper.MustBeSigned and machoHasCodeSignature so
broken or unreadable darwin/arm64 helpers cannot pass validation.
In `@docs/specs/ssh-executor/spec.md`:
- Around line 84-85: Update the later scope sections in the ssh-executor spec to
match the new support matrix by removing linux/arm64 from any “Out of scope” and
“Future scope” mentions. Keep the platform list consistent with the supported
remote platforms and runtime bundle names already described earlier in the
document, so the scope sections no longer contradict the main support matrix.
🪄 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: 8cfb97a5-7019-4aa3-b5f0-feb257197998
📒 Files selected for processing (15)
.github/workflows/release.ymlapps/backend/Makefileapps/backend/internal/agent/runtime/lifecycle/executor_ssh_connection_test.goapps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.goapps/backend/internal/launcher/bundle.goapps/backend/internal/launcher/bundle_test.goapps/backend/internal/task/service/service_turns.goapps/backend/internal/task/service/service_turns_test.goapps/desktop/e2e/desktop-launch-smoke.mjsapps/desktop/src-tauri/src/backend.rsdocs/specs/ssh-executor/spec.mdscripts/release-desktop.test.shscripts/release/package-bundle.shscripts/release/prepare-desktop-runtime.shscripts/release/verify-desktop-runtime.sh
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38022b8961
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Thanks for the PR. A few things I think need fixing before merge:
Small docs cleanup: |
FindingsSuggestion (recommended, doesn't block)
Summary
Verdict: Ready with suggestions — the three fixes are solid and well-tested overall. Suggestions 1 and 3 improve future debuggability; suggestion 2 is a defensive guard. None blocks merge. |
Follow-up fixes for carlosflorencio's review of the SSH darwin/arm64 PR. Packaging (linux/arm64 was half-wired — built but not shipped): - root Makefile service-bundle: copy agentctl-linux-arm64 into dist/kandev/bin (package-bundle.sh already requires it, so `make service-bundle` was broken). - Dockerfile: COPY + chmod agentctl-linux-arm64 so the GHCR image can serve ARM Linux SSH remotes. - apps/backend/Makefile: define the adhoc_sign_darwin macro per-OS so the Windows (cmd.exe) recipe path no longer runs POSIX shell; it's a no-op on Windows (can't codesign darwin there anyway). Terminal / workspace-restore fallback (service_turns.go): - Project SSH executor config through a dedicated stable-key allowlist (FilterSSHWorkspaceFallbackConfig) instead of FilterPersistentMetadata: now includes ssh_host_alias (alias-only executors restore correctly) and excludes session-scoped runtime keys (remote agentctl port/PID/session dir) so a restore can't reattach to a stale remote instance. - Gate the fallback to ExecutorTypeSSH. Credential forwarding (executor_ssh_operations.go + orchestrator): - Drop the os.Getenv fallback in sshRemoteAgentEnv; forward only keys the orchestrator explicitly resolved into req.Env, so the control plane's own process credentials can't leak to arbitrary SSH hosts. - Resolve credentials for SSH executors in the orchestrator (executorNeedsResolvedCredentials) so env-authenticated agents still work via profile env vars / remote_auth_secrets / GITHUB_TOKEN chain. Bundle validation (bundle.go): fail closed when a MustBeSigned darwin/arm64 helper can't be parsed as a thin Mach-O (read error / truncation / fat), instead of treating "undeterminable" as signed. Docs: remove linux/arm64 from the ssh-executor spec Out-of-scope / Future-scope sections; add agentctl-linux-arm64 to apps/desktop/AGENTS.md runtime layout. Tests: updated TestSSHRemoteAgentEnv (no control-plane fallback), extended TestMergeExecutorConfigMetadata (alias + session-scoped exclusion), added a service-level GetWorkspaceInfoForSession SSH-restore test, a bundle fail-closed test, and executorNeedsResolvedCredentials coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks so much for the careful review, @carlosflorencio 🙏 — every one of the six points was spot on, and a couple of them (the bundle copy gaps especially) would have bitten real users. Pushed fixes for all of them. Also grateful to @coderabbitai, @greptile-apps, @chatgpt-codex-connector and the CI review bot — their findings overlapped and reinforced yours, and I've credited the specific catches below. Here's what changed and why: Packaging —
|
|
To use Codex here, create an environment for this repo. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== bundle.go machoHasCodeSignature / validateRuntimeBundle ==="
fd -a bundle.go apps/backend/internal/launcher | xargs -I{} sed -n '1,200p' {}
echo "=== root Makefile copy list ==="
fd -a "^Makefile$" --max-depth 1 | xargs -I{} grep -n "agentctl" {}
echo "=== Dockerfile ==="
fd -a "^Dockerfile$" --max-depth 2 | xargs -I{} grep -n "agentctl" {}
echo "=== FilterSSHWorkspaceFallbackConfig ==="
rg -n "FilterSSHWorkspaceFallbackConfig|FilterPersistentMetadata|ssh_host_alias" apps/backend/internal --type=go
echo "=== sshRemoteAgentEnv ==="
rg -n "sshRemoteAgentEnv|os.Getenv|executorNeedsResolvedCredentials" apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go apps/backend/internal/orchestrator 2>/dev/null
echo "=== apps/backend/Makefile signing macro ==="
rg -n "adhoc_sign_darwin|Windows_NT" apps/backend/MakefileLength of output: 11585
Nice work tightening all of these up, especially the credential-forwarding change — routing through On the retitle suggestion ("add linux/arm64 SSH remote support" vs "restore") — that's a call for (ノ◕ヮ◕)ノ*:・゚✧ |
|
This is a thorough and well-reasoned follow-up — the fixes address every point cleanly, and several of them go meaningfully beyond the minimum. A few specific callouts:
Credential resolution for SSH ( Fail-closed on On the PR title: agree with the self-correction. "Add linux/arm64 SSH remote support" is more accurate than "restore" given the history, and squash commit messages become changelog entries — worth getting right. Happy to see that as a retitle on the next push. On the The behavior change note (env-authenticated agents now need tokens via executor profile, not host env) is well-placed and clearly worded. Anyone who hit this in a working setup will know exactly what changed and why. LGTM overall — the fixes hold up. Tip: You can customize Greptile's behavior for this repo with |
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
…lable ctx Addresses cubic-dev-ai's P2 on kdlbs#1559. The resume/launch ready-waits are passed context.WithoutCancel(ctx) so they survive the caller's request timeout, but that context has no deadline — and waitForSessionReady only checked the 90s wall-clock budget between poll iterations, so a single blocking GetTaskSession query could hang past the intended timeout. Derive an internal context.WithTimeout(ctx, maxWait) and use it for both the poll select and the GetTaskSession calls, mirroring what waitForAgentPromptReady already does. waitForAgentPromptReady was already self-bounded and is unchanged. Test: TestWaitForSessionReady_BoundsQueriesWithNonCancellableContext asserts the query receives a context carrying a deadline even when the caller passes a non-cancellable context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @cubic-dev-ai — valid catch. Fixed in
|
@iamcobolt Thanks for the detailed writeup — the fix looks solid. The key insight you're solving is that One thing I'd double-check: the All good from my end — thanks for handling this cleanly. Tip: get faster answers by chatting with cubic’s review copilot in the review UI |
…1582) These resume/launch readiness-context changes were swept into this SSH-executor PR by accident and are unrelated to it. Reverted to origin/main here; they now live in their own PR (kdlbs#1582) so this PR stays scoped to the SSH executor darwin/arm64 + review fixes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Housekeeping: the orchestrator resume/launch readiness-context change (and the |
FindingsSuggestion (recommended, doesn't block)
Summary
Verdict: Ready to merge with suggestions. The three fixes are focused and correct. |
|
OpenCode findings that could not be posted inline OpenCode model: Some findings could not be posted inline, so they are preserved here. Inline placement unavailableapps/backend/internal/orchestrator/executor/executor_execute.go:840 - applyContainerCredentials name no longer matches its scope
Fix: rename to |
CI's changed-file golangci-lint flagged: goconst on the credential env-var
literals (constantized as envKey* — several also recur in the Docker/Sprites
paths), dupword in a bundle test assertion ('true true'), and staticcheck S1009
(redundant nil check before len()) in the executor-config merge test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
apps/backend/internal/agent/runtime/lifecycle/executor_backend.go (1)
209-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive the fallback allowlist from the trusted SSH keys to keep the shared connection-routing set in one place; copy
trustedExecutorConfigKeysand addMetadataKeySSHWorkdirRootandMetadataKeySSHShellso restore and launch stay aligned.🤖 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 `@apps/backend/internal/agent/runtime/lifecycle/executor_backend.go` around lines 209 - 231, Derive the SSH workspace fallback allowlist from the shared trusted key set instead of duplicating entries in sshWorkspaceFallbackKeys; reuse trustedExecutorConfigKeys as the base and add only MetadataKeySSHWorkdirRoot and MetadataKeySSHShell so executor_backend.go keeps restore and launch routing aligned. Update the sshWorkspaceFallbackKeys initialization to reference the same connection-routing keys used by targetFromMetadata and preserve the two workspace-specific keys needed by the terminal path..github/workflows/release.yml (1)
1049-1051: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider
persist-credentials: falseon checkout steps that don't need to push.Several
actions/checkoutsteps touched by this PR (adding the newref: ${{ needs.prepare.outputs.ref }}line for the restored linux/arm64 flow) leave the defaultpersist-credentials: true, so the ephemeralGITHUB_TOKENis written to the local git config for the rest of the job (docker builds, npm publish, homebrew tap update). Static analysis (zizmorartipacked) flags this across all these jobs. Since none of these jobs need togit pushafter checkout, settingpersist-credentials: falsereduces the blast radius if a build step or dependency is compromised.🔒 Example fix (repeat per flagged checkout step)
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.ref }} + persist-credentials: falseAlso applies to: 1120-1122, 1233-1235, 1285-1287, 1388-1391, 1494-1496, 1526-1528
🤖 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 @.github/workflows/release.yml around lines 1049 - 1051, The checkout steps in the release workflow now use a restored ref but still keep the default credential persistence, which leaves the GITHUB_TOKEN in git config for later job steps. Update each affected actions/checkout usage in release.yml to set persist-credentials: false alongside the existing ref: ${{ needs.prepare.outputs.ref }} (and the other flagged checkout blocks) since these jobs do not need to push after checkout. Focus on the checkout steps in the restored linux/arm64 flow and the other touched jobs that run docker builds, npm publish, or homebrew tap updates.Source: Linters/SAST tools
🤖 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.
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 1049-1051: The checkout steps in the release workflow now use a
restored ref but still keep the default credential persistence, which leaves the
GITHUB_TOKEN in git config for later job steps. Update each affected
actions/checkout usage in release.yml to set persist-credentials: false
alongside the existing ref: ${{ needs.prepare.outputs.ref }} (and the other
flagged checkout blocks) since these jobs do not need to push after checkout.
Focus on the checkout steps in the restored linux/arm64 flow and the other
touched jobs that run docker builds, npm publish, or homebrew tap updates.
In `@apps/backend/internal/agent/runtime/lifecycle/executor_backend.go`:
- Around line 209-231: Derive the SSH workspace fallback allowlist from the
shared trusted key set instead of duplicating entries in
sshWorkspaceFallbackKeys; reuse trustedExecutorConfigKeys as the base and add
only MetadataKeySSHWorkdirRoot and MetadataKeySSHShell so executor_backend.go
keeps restore and launch routing aligned. Update the sshWorkspaceFallbackKeys
initialization to reference the same connection-routing keys used by
targetFromMetadata and preserve the two workspace-specific keys needed by the
terminal path.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 228ae798-ff86-470f-909e-be61dfffe2c5
📒 Files selected for processing (15)
.github/workflows/release.ymlDockerfileMakefileapps/backend/Makefileapps/backend/internal/agent/runtime/lifecycle/executor_backend.goapps/backend/internal/agent/runtime/lifecycle/executor_ssh_connection_test.goapps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.goapps/backend/internal/launcher/bundle.goapps/backend/internal/launcher/bundle_test.goapps/backend/internal/orchestrator/executor/executor_credentials_test.goapps/backend/internal/orchestrator/executor/executor_execute.goapps/backend/internal/task/service/service_turns.goapps/backend/internal/task/service/service_turns_test.goapps/desktop/AGENTS.mddocs/specs/ssh-executor/spec.md
✅ Files skipped from review due to trivial changes (2)
- apps/desktop/AGENTS.md
- docs/specs/ssh-executor/spec.md
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/backend/internal/launcher/bundle_test.go
- apps/backend/Makefile
- apps/backend/internal/launcher/bundle.go
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
good memory optimization Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Fixes found while live-validating #1534 (now merged) against real Apple Silicon Macs over Tailscale. Three focused commits, each with a unit test;
go build ./...and the affected packages (lifecycle,task/service,launcher) pass against currentmain.1. Forward agent credential env to remote SSH agents (blocks Claude tasks)
Env-authenticated agents (
claude-acpreadsCLAUDE_CODE_OAUTH_TOKEN, not a credentials file) fail on every SSH remote withAuthentication required. The SSH executor'sCreateInstanceRequestnever setEnv, so agentctl'sCollectAgentEnv(req.Env)had nothing to merge (Docker usesEnv: req.Env).Now SSH executors run the same credential-resolution path used by remote/containerized executors, then forward only a credential allowlist from the resolved request env:
CLAUDE_CODE_OAUTH_TOKEN,ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY,GOOGLE_API_KEY,GITHUB_TOKEN, andGH_TOKEN.This deliberately does not fall back to the control-plane process environment. Forwarding arbitrary host env to an SSH target would bypass per-executor credential scoping and could leak local credentials to a remote host. Empty values are skipped so remote-side env is not clobbered with blanks.
2. Project executor SSH config for terminal / workspace-restore
Opening a terminal or restoring a finished SSH task throws
ssh executor: host (or host_alias) is required in executor config.GetWorkspaceInfoForSessioncopied onlyExecutorTypeand droppedexec.Config(ssh_host, fingerprint, user); those keys only survived via a liveExecutorRunningrecord, so terminal-state / post-restart / post-cleanup sessions had no host config.Now the service projects a stable SSH executor-config allowlist into workspace metadata as a fallback. Existing live metadata still wins, alias-only executors are preserved, and session-scoped runtime keys such as remote agentctl port/PID/session dir are intentionally excluded so restore cannot reattach to a stale remote instance.
3. Restore
linux/arm64+ enforce signed darwin helperlinux/arm64was dropped vs the predecessor branch (platform gate,build-agentctl-remote, bundle/desktop validators all omitted it), so ARM Linux remotes were rejected. This restores linux/arm64 across the platform gate, build targets, Docker/service bundle packaging, desktop runtime validators, and release scripts.The PR also makes darwin helper ad-hoc signing explicit and has bundle validation reject an unsigned
agentctl-darwin-arm64, which Apple Silicon would otherwise SIGKILL at launch.Validation
Live end-to-end on
darwin/arm64over Tailscale: signedagentctluploads, launches, binds, and a full Claude task completes (stop_reason: end_turn) once credentials reach the agent.Local validation listed in commits includes
go build ./..., affected Go package tests, release desktop runtime checks, and changed-file lint fixes from review.Generated with Claude Code