Skip to content

feat: support remote macos ssh hosts#1534

Merged
carlosflorencio merged 8 commits into
mainfrom
feature/discord-general-darw-7wh
Jun 29, 2026
Merged

feat: support remote macos ssh hosts#1534
carlosflorencio merged 8 commits into
mainfrom
feature/discord-general-darw-7wh

Conversation

@carlosflorencio

@carlosflorencio carlosflorencio commented Jun 29, 2026

Copy link
Copy Markdown
Member

Remote SSH hosts should not require Kandev to be installed locally on every Mac just to run agents. The SSH executor now packages and selects macOS agentctl helpers, so one control plane can drive trusted Linux amd64 and macOS SSH hosts.

Important Changes

  • Added platform detection for SSH hosts using uname -s and uname -m, with support for linux/amd64, darwin/arm64, and darwin/amd64.
  • Made AgentctlResolver, bundle validation, desktop runtime validation, release scripts, and release packaging require platform-specific remote helpers.
  • Updated Test Connection API/UI/E2E from raw architecture checks to normalized platform checks.
  • Added a Darwin no-cgo metrics fallback so cross-compiled macOS agentctl helpers build with CGO_ENABLED=0.

Validation

  • make fmt
  • node apps/web/scripts/generate-release-notes.mjs
  • node apps/web/scripts/generate-changelog.mjs
  • make typecheck
  • make test
  • make lint
  • make -C apps/backend build-agentctl-remote
  • go test ./internal/agent/runtime/lifecycle ./internal/ssh ./internal/launcher ./internal/system/metrics
  • bash scripts/release-desktop.test.sh
  • cargo test in apps/desktop/src-tauri
  • KANDEV_E2E_CONTAINERS=1 pnpm e2e --project=containers tests/ssh/test-endpoint.spec.ts -g "returns step ordering"
  • KANDEV_E2E_CONTAINERS=1 pnpm e2e --project=containers tests/ssh/test-result.spec.ts -g "uname output surfaces"
  • pnpm e2e --project=chromium tests/settings/ssh-profile-connection-link.spec.ts
  • pnpm e2e --project=mobile-chrome tests/settings/mobile-ssh-profile-connection-link.spec.ts
  • git diff --check

Possible Improvements

Medium risk: macOS helper build and resolver paths are covered locally, but a live macOS SSH host should still be exercised manually or in future CI.

Checklist

  • I have performed a self-review of my code.
  • I have manually tested my changes and they work as expected.
  • My changes have tests that cover the new functionality and edge cases.
  • If my change touches UI files (apps/web/), I have added or updated Playwright e2e tests in apps/web/e2e/ and verified them with make test-e2e.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The SSH executor is extended from Linux/amd64-only to support macOS (darwin/arm64, darwin/amd64) remote hosts. This involves cross-compiling platform-specific agentctl helpers, introducing a SSHRemotePlatform abstraction throughout the backend lifecycle, updating bundle validation across native launcher, desktop, and release scripts, and adding a "Connection Settings" shortcut button on SSH executor profile pages.

Changes

macOS remote SSH executor support

Layer / File(s) Summary
Cross-compile build targets for darwin agentctl helpers
apps/backend/Makefile, Makefile, .github/workflows/release.yml, Dockerfile, apps/cli/src/release-config.test.ts
Adds build-agentctl-darwin-arm64, build-agentctl-darwin-amd64, and build-agentctl-remote umbrella targets; replaces linux-only conditional builds; updates CI codesigning loop and Dockerfile COPY/chmod to include darwin helpers.
SSHRemotePlatform type, normalization, and AgentctlResolver
apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go, apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go, apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver_test.go, apps/backend/internal/agent/runtime/lifecycle/executor_ssh_connection_test.go
Introduces SSHRemotePlatform struct, uname-based OS/arch normalization, requireSupportedRemotePlatform gate, SSHDefaultShellForPlatform; rewrites ResolveRemoteBinary with env var lookup and platform-suffixed helper search; removes SSHRequireSupportedArch.
SSH executor lifecycle platform threading
apps/backend/internal/agent/runtime/lifecycle/executor_ssh.go, apps/backend/internal/agent/runtime/lifecycle/executor_ssh_credentials.go, apps/backend/internal/agent/runtime/lifecycle/executor_ssh_credentials_test.go, apps/backend/internal/ssh/handlers.go, apps/backend/internal/ssh/handlers_test.go
Threads SSHRemotePlatform through CreateInstance, prepareRemoteHost, uploadCredentials, runAuthSetupScripts, resolveRemoteAuthHomeDir; adds sshShellForRemote (zsh on Darwin); updates SSH test handler to probe platform and return os/platform fields; adds readinessShellForRequest helper.
Bundle and runtime validation for all remote helpers
apps/backend/internal/launcher/bundle.go, apps/backend/internal/launcher/bundle_test.go, apps/desktop/src-tauri/src/backend.rs, apps/backend/internal/system/metrics/collector_darwin_nocgo.go, scripts/release/..., scripts/release-desktop.test.sh, apps/desktop/e2e/desktop-launch-smoke.mjs
Replaces single agentctl-linux-amd64 helper checks with REMOTE_AGENTCTL_HELPERS loops across launcher validation, desktop Tauri runtime validation, release packaging, preparation, and verification scripts; adds Darwin no-cgo metrics stub.
Web UI: Connection Settings action, shell selector, and platform types
apps/web/app/settings/executors/[profileId]/page.tsx, apps/web/components/settings/profile-edit/profile-edit-page-chrome.tsx, apps/web/components/settings/ssh-agent-readiness-card.tsx, apps/web/components/settings/ssh-agent-readiness-card.test.ts, apps/web/lib/types/http-ssh.ts, apps/web/e2e/tests/...
Adds actions prop to ProfileHeader; renders Connection Settings button on SSH profile pages; adds readinessProbeBody/readinessDisplayShell helpers and defaultShell state; updates HTTP SSH types with os/platform/default_shell fields; updates e2e tests to assert verify-platform step and linux/amd64 output.
Documentation and spec updates
docs/specs/ssh-executor/spec.md, docs/specs/ssh-executor/e2e-plan.md, docs/specs/native-kandev-cli/..., docs/plans/desktop-tauri-app/..., apps/cli/README_internal.md, apps/desktop/AGENTS.md
Updates bundle layout trees, SSH executor spec, and e2e plans to reflect multi-platform agentctl helpers and the new Connection Settings UX requirement.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Web Client
    participant SSHHandlers as ssh/handlers.go
    participant SSHExecutor as SSHExecutor.CreateInstance
    participant detectRemoteInfo
    participant AgentctlResolver
    participant RemoteHost as Remote SSH Host

    Client->>SSHHandlers: POST /ssh/test
    SSHHandlers->>detectRemoteInfo: uname -m, uname -s
    detectRemoteInfo->>RemoteHost: run uname commands
    RemoteHost-->>detectRemoteInfo: OS/arch strings
    detectRemoteInfo-->>SSHHandlers: SSHRemotePlatform (e.g. darwin/arm64)
    SSHHandlers->>SSHHandlers: requireSupportedRemotePlatform
    SSHHandlers-->>Client: TestResult{OS, Platform}

    Client->>SSHExecutor: CreateInstance
    SSHExecutor->>detectRemoteInfo: probe platform
    detectRemoteInfo-->>SSHExecutor: SSHRemotePlatform
    SSHExecutor->>AgentctlResolver: ResolveRemoteBinary(platform)
    AgentctlResolver-->>SSHExecutor: agentctl-darwin-arm64 path
    SSHExecutor->>RemoteHost: upload + forward agentctl (zsh shell on darwin)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • kdlbs/kandev#927: Introduces the SSH executor and its initial agentctl provisioning/caching flow in the same lifecycle codepaths that this PR generalizes to multi-platform.
  • kdlbs/kandev#1519: Modifies scripts/release/verify-desktop-runtime.sh's is_executable_file Windows handling for agentctl-linux-amd64, the same code path this PR extends to cover darwin helpers.

Poem

🐰 Hippity hop, from Linux to Mac,
The agentctl helpers have darwin's back!
arm64 and amd64, both in the bin,
The SSH executor grins with a grin.
Cross-compiled with CGO set to none,
Remote macOS support — finally done! 🍎

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding support for remote macOS SSH hosts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description includes a concise summary, key changes, validation, and the checklist, with no blocking template omissions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/discord-general-darw-7wh

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.

❤️ Share

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

@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 5s —— View job


I'll analyze this and get back to you.

Comment thread apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown

Greptile Summary

This PR extends the SSH executor to support remote macOS hosts (darwin/arm64 and darwin/amd64) alongside the existing linux/amd64 target, so a single control plane can drive Linux and macOS SSH hosts without installing Kandev locally on each Mac.

  • Adds uname -s + uname -m platform detection in detectRemoteInfo, normalizing raw uname strings into typed SSHRemotePlatform{GOOS, GOARCH} tuples that flow through the entire SSH lifecycle — agentctl resolver, upload, shell selection, credentials, and bundle validation.
  • Cross-compiles three remote agentctl helpers (linux-amd64, darwin-arm64, darwin-amd64) in CI, Dockerfile, Makefile, and release scripts; updates bundle validation in Go, Rust (Tauri), and shell scripts to unconditionally require all three.
  • Defaults the login shell to zsh on darwin (over the existing bash default) and surfaces a new platform field in the test-connection API and UI; adds a "Connection Settings" shortcut button to the SSH profile editor.

Confidence Score: 5/5

Safe to merge; the platform detection, shell selection, binary resolution, and bundle validation are consistent across Go, Rust, shell scripts, and CI with good test coverage at every layer.

The change is well-scoped: new platform paths (darwin/arm64, darwin/amd64) are additive, all existing linux/amd64 behavior is preserved, the normalization/support-gate logic is unit-tested, and bundle validation is now unconditional. The one gap — an inline CI check missing agentctl-linux-amd64 — is fully covered by verify-desktop-runtime.sh invoked earlier in the same pipeline.

apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go carries the core platform detection logic and is worth a final read; .github/workflows/release.yml has the minor inline-check omission noted above.

Important Files Changed

Filename Overview
apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go Core platform detection logic: adds uname -s probe, SSHRemotePlatform struct, normalization functions, requireSupportedRemotePlatform, and SSHDefaultShellForPlatform. All three supported platforms are correctly handled and the unsupported path returns a populated struct (per the prior-thread fix).
apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go Refactors resolver to select platform-specific agentctl helper; adds legacy linux/amd64 env-var fallback, same-platform native binary fallback for dev mode, and clear error messages naming the env var and platform. Well-tested.
apps/backend/internal/agent/runtime/lifecycle/executor_ssh.go Threads SSHRemotePlatform through prepareRemoteHost, preflightAgentBinary, maybeUploadCredentials, and startAndForwardAgentctl; switches shell selection to sshShellForRemote for platform-aware defaults. Changes are consistent and all callers updated.
apps/backend/internal/ssh/handlers.go Adds probeRemotePlatform helper used by probeAgents and probeShells to determine platform-aware default shell; extends TestResult with os and platform fields; renames Verify arch to Verify platform step. Logic is correct with graceful fallback to bash when probe fails.
apps/backend/internal/launcher/bundle.go Replaces conditional linux/amd64 helper check with an unconditional loop over all three required remote helpers. Removes the now-dead requiresAgentctlLinuxAMD64 function.
apps/desktop/src-tauri/src/backend.rs Adds REMOTE_AGENTCTL_HELPERS constant array and iterates it in validate_runtime_dir and test helpers. Adds test for missing darwin helper. Consistent with Go and shell-script validation.
.github/workflows/release.yml Adds darwin helper cross-compilation and bundle packaging. The inline macOS runtime binary check at line 599 lists darwin helpers but omits agentctl-linux-amd64, leaving a gap in the CI gate (though verify-desktop-runtime.sh still covers all three).
apps/backend/internal/system/metrics/collector_darwin_nocgo.go New no-op metrics fallback for darwin && !cgo build tag, enabling CGO_ENABLED=0 cross-compilation of darwin agentctl helpers without Darwin SDK.
apps/web/components/settings/ssh-agent-readiness-card.tsx Adds readinessProbeBody / readinessDisplayShell helpers; tracks defaultShell from server responses; omits explicit shell in probe body when field is empty so the backend can apply its platform default. Unit-tested.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant C as SSH Client (control plane)
    participant R as Remote Host (Linux/macOS)
    participant A as AgentctlResolver
    participant B as Bundle (bin/)

    C->>R: uname -a (info only)
    C->>R: uname -m → Arch (x86_64 / arm64)
    C->>R: uname -s → OS (Linux / Darwin)
    note over C: normalizeSSHRemotePlatform()<br/>→ SSHRemotePlatform{GOOS,GOARCH}
    C->>C: "requireSupportedRemotePlatform()<br/>linux/amd64 ✓ darwin/arm64 ✓ darwin/amd64 ✓"

    C->>A: ResolveRemoteBinary(platform)
    A->>B: "lookup agentctl-{goos}-{goarch}"
    B-->>A: /bin/agentctl-darwin-arm64
    A-->>C: binary path

    C->>R: SCP agentctl helper (if sha256 differs)
    C->>R: chmod +x agentctl
    note over C: SSHDefaultShellForPlatform()<br/>darwin → zsh, linux → bash
    C->>R: "{shell} -lc 'agentctl serve …'"
    R-->>C: port forward ready
Loading
%%{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 C as SSH Client (control plane)
    participant R as Remote Host (Linux/macOS)
    participant A as AgentctlResolver
    participant B as Bundle (bin/)

    C->>R: uname -a (info only)
    C->>R: uname -m → Arch (x86_64 / arm64)
    C->>R: uname -s → OS (Linux / Darwin)
    note over C: normalizeSSHRemotePlatform()<br/>→ SSHRemotePlatform{GOOS,GOARCH}
    C->>C: "requireSupportedRemotePlatform()<br/>linux/amd64 ✓ darwin/arm64 ✓ darwin/amd64 ✓"

    C->>A: ResolveRemoteBinary(platform)
    A->>B: "lookup agentctl-{goos}-{goarch}"
    B-->>A: /bin/agentctl-darwin-arm64
    A-->>C: binary path

    C->>R: SCP agentctl helper (if sha256 differs)
    C->>R: chmod +x agentctl
    note over C: SSHDefaultShellForPlatform()<br/>darwin → zsh, linux → bash
    C->>R: "{shell} -lc 'agentctl serve …'"
    R-->>C: port forward ready
Loading

Reviews (6): Last reviewed commit: "fix: address ssh readiness review follow..." | Re-trigger Greptile

@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 1m 47s —— View job


Code Review

  • Gather context (PR diff, existing reviewer comments)
  • Review tests and verification
  • Review implementation (security, architecture, correctness, complexity)
  • Post findings

I reviewed the full diff against origin/main (36 files, +768/-195), focusing on the Go backend platform-detection/resolver changes, the desktop Rust runtime validation, bundle/release packaging, and the frontend copy updates. Inline comments posted on the specific lines.

Findings

Blocker (must fix before merge)

  1. normalizeSSHRemotePlatform discards the normalized platform on the unsupported branchapps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go:175-183
    • Issue: When requireSupportedRemotePlatform rejects a parseable-but-unsupported platform (e.g. linux/arm64), the function returns an empty SSHRemotePlatform{} instead of the populated struct.
    • Why: detectRemoteInfo (lines 163-167) then falls back to a struct with only UnameOS/UnameArch set, so .String() returns "unknown". That value flows straight into the TestResult.Platform JSON field returned to the UI (apps/backend/internal/ssh/handlers.go:552), losing the useful normalized value (linux/arm64) for a cosmetic-but-real regression in the failure path. Doesn't affect any of the three currently-supported platforms, but bites the moment linux/arm64 support is added.
    • Fix: return platform, false instead of SSHRemotePlatform{}, false (posted as an inline suggestion). Also independently flagged by the Greptile auto-review on this PR — fixing it would close out that finding too.

Suggestion (recommended, doesn't block)

  1. Host-match fallback in agentctlBinaryCandidates is undocumented and masks missing helper builds in devapps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go:104-113

    • When the remote platform matches the control-plane's own runtime.GOOS/GOARCH, the resolver falls back to the un-suffixed agentctl binary. Reasonable dev convenience, but it silently hides a forgotten make build-agentctl-remote on same-platform remotes. Already flagged by an automated review (OpenCode) on this PR; the suggested fix is just moving/clarifying the existing explanatory comment to sit above the if, since release bundles are validated separately and unaffected.
  2. Env-var resolution order in ResolveRemoteBinaryapps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go:44-57

    • If the primary platform env var (e.g. KANDEV_AGENTCTL_LINUX_AMD64_BINARY) is set but points to a missing path, resolution fails immediately rather than falling through to the legacy KANDEV_AGENTCTL_LINUX_BINARY or the executable-relative candidates. Likely intentional (fail loud on a bad explicit override), but worth confirming, and untested by agentctl_resolver_test.go.

Other notes (not filed as findings — informational)

  • Tests are reasonably thorough for the new resolver/platform logic (agentctl_resolver_test.go, the new cases in executor_ssh_connection_test.go), the no-cgo Darwin metrics stub, the Rust backend.rs runtime-validation tests, and the e2e platform-assertion updates. An automated review (OpenCode) on this PR additionally flagged agentctlBinaryCandidates' relative-path ordering and the not-found error message as undocumented/under-tested — those are reasonable test additions but not blockers given the existing coverage of the core resolution paths.
  • collector_darwin_nocgo.go build tag (darwin && !cgo) is correctly mutually exclusive with the existing cgo-based Darwin collector.
  • Bundle validation (bundle.go), release scripts, and the desktop Rust validate_runtime_dir all consistently require the three remote helpers — no gaps found there.
  • Frontend copy/UI changes (ssh-connection-card.tsx, ssh-create-page.tsx, profile header "Connection Settings" link) are straightforward and consistent with the new platform support; no concerns.

Summary

Severity Count
Blocker 1
Suggestion 2

Verdict: Blocked — fix the platform-normalization blocker before merge (small, well-scoped fix); the two suggestions are good follow-ups but don't need to gate this PR.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

OpenCode review complete

OpenCode model: opencode-go/minimax-m3

No suggestions found for commit 5d859cc3b59a.

Comment thread apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

output_base="$(basename "$OUTPUT_DIR")"
if resolved_parent="$(cd "$output_parent" 2>/dev/null && pwd -P)"; then
resolved_output="$resolved_parent/$output_base"

P2 Badge Sign bundled Darwin helper binaries

This now copies agentctl-darwin-* Mach-O executables into the desktop runtime resources, but the macOS release workflow’s “Sign macOS desktop runtime binaries” loop still signs only kandev and agentctl. In signed/notarized macOS desktop builds, these nested executable helpers can remain unsigned even though they are shipped inside the app bundle; include the Darwin helper names in that signing step before Tauri packages the resources.

ℹ️ 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".

Comment thread .github/workflows/release.yml
Comment thread apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 2m 12s —— View job


Review

  • Gather context (PR diff, prior review comments, fix commits)
  • Verify fixes for previously-flagged blocker/suggestions
  • Review tests and verification
  • Review implementation (security, architecture, correctness, complexity)
  • Post findings

This is a follow-up pass after commits 8c543cd (resolver test coverage) and 1ae1104 (platform-normalization fix) addressed the prior review round.

Verified fixes from the previous round

  • Blocker resolvednormalizeSSHRemotePlatform (executor_ssh_operations.go:172-180) now returns the populated platform struct on the unsupported branch instead of SSHRemotePlatform{}. detectRemoteInfo and TestResult.Platform now correctly surface linux/arm64 (or other parseable-but-unsupported tuples) instead of "unknown". New test cases in executor_ssh_connection_test.go assert this directly.
  • Suggestion resolved — the host-match fallback in agentctlBinaryCandidates (agentctl_resolver.go:104-107) now has an explanatory comment about the dev-convenience/bundle-validation trade-off.
  • Suggestion resolved (test coverage)agentctl_resolver_test.go now covers primary-vs-legacy env priority, candidate ordering (including the host-fallback branch and its absence on differing platforms), and the not-found error message contents — closing the gaps OpenCode's automated review flagged.
  • bundle.go's helper-validation loop and ssh/handlers.go's testProbeAndPlatform/testAgentctlCache plumbing are consistent and correctly threaded through; no new issues there.

Findings

Suggestion (recommended, doesn't block)

  1. New Darwin agentctl helpers aren't covered by the macOS signing step.github/workflows/release.yml (signing loop ~line 599; helper build/copy at lines 423-436, modified by this PR)
    • Issue: This PR's update to scripts/release/prepare-desktop-runtime.sh now copies agentctl-darwin-arm64/agentctl-darwin-amd64 into apps/desktop/src-tauri/resources/kandev/bin. The "Sign macOS desktop runtime binaries" step later in the same workflow still only iterates for binary in kandev agentctl.
    • Why: on a signed/notarized macOS desktop build, the two new nested Mach-O executables ship unsigned inside the app bundle, which can fail Gatekeeper/notarization checks.
    • Fix: extend that loop to also sign agentctl-darwin-arm64 and agentctl-darwin-amd64.
    • Note: this was already flagged by the Codex auto-review on this PR as a P2 and remains unaddressed in the latest fix commits — posted as an inline comment for visibility.

Summary

Severity Count
Blocker 0
Suggestion 1

Verdict: Ready with suggestions — the prior blocker and both suggestions from the earlier review round are fixed and well-tested; one new, low-risk suggestion (macOS signing coverage for the new Darwin helpers) is worth picking up before cutting a signed desktop release, but doesn't block this PR.

Comment thread .github/workflows/release.yml
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude Code is working…

I'll analyze this and get back to you.

View job run

@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 1m 30s —— View job


Review

  • Gather context (PR diff, prior review rounds, latest fix commits)
  • Review latest commit (2946a16 — sign darwin ssh helper binaries) against prior findings
  • Review tests and verification
  • Review implementation (security, architecture, correctness, complexity)
  • Post findings

This is a final pass after 2946a16 ("fix: sign darwin ssh helper binaries"), the last in a series of fix commits (8c543cd, 1ae1104, b57f79e3, 2946a16) addressing every blocker/suggestion raised across the previous review rounds (mine, Greptile, OpenCode, Codex).

Verified fixes

  • Blocker resolvednormalizeSSHRemotePlatform (executor_ssh_operations.go:172-180) now returns the populated platform struct on the unsupported branch instead of SSHRemotePlatform{}, so linux/arm64-style unsupported tuples surface correctly in TestResult.Platform instead of "unknown". requireSupportedRemotePlatform (lines 200-216) also has a defensive fallback that reconstructs UnameOS/UnameArch if .String() is "unknown".
  • Resolver test coverageagentctl_resolver_test.go now has 7 tests covering primary-vs-legacy env priority, the bad-primary-env-doesn't-fall-back-to-legacy case, candidate ordering with/without the host-match fallback, and the not-found error message contents.
  • Host-match fallback documented — the dev-convenience/bundle-validation trade-off comment now sits above the if in agentctlBinaryCandidates (agentctl_resolver.go).
  • Darwin helpers shipped in the Docker imageDockerfile:99-112 now COPYs and chmod +x's agentctl-darwin-arm64/agentctl-darwin-amd64 alongside agentctl-linux-amd64, closing the P1 gap where ghcr.io/kdlbs/kandev would have failed every macOS SSH launch.
  • Darwin helpers signed.github/workflows/release.yml:599 ("Sign macOS desktop runtime binaries") now iterates kandev agentctl agentctl-darwin-arm64 agentctl-darwin-amd64, so the new nested Mach-O executables get codesign'd before Tauri packages the desktop bundle (this was the one open item from the prior round).
  • zsh default for Darwin SSH profilessshShellForRemote (executor_ssh.go:678-686) now defaults Darwin remotes to zsh when no ssh_shell is set in profile metadata, consistently wired through all 4 call sites (executor_ssh.go:287,600, executor_ssh_credentials.go:101,210) and covered by executor_ssh_credentials_test.go:53-69 (explicit override, Darwin default, Linux default-marker).
  • Env-var fallback semantics confirmed intentionalTestAgentctlResolverLinuxAMD64BadPrimaryEnvDoesNotUseLegacyFallback pins the fail-loud-on-bad-override behavior I'd flagged as worth confirming.

Findings

No new blockers or suggestions. I checked every file changed by the 2946a16 commit (just the one-line signing-loop change) and re-verified the cumulative diff against origin/main — bundle validation (bundle.go), the Rust validate_runtime_dir (backend.rs), verify-desktop-runtime.sh, package-bundle.sh, and prepare-desktop-runtime.sh all consistently reference the three remote helpers, and the frontend platform-display changes remain straightforward.

I wasn't able to execute go test/make test in this sandbox (command required approval that wasn't available in review-only mode), so this pass is based on static reading of the diff and tests rather than a live run — the PR description's listed validation commands (including the targeted go test ./internal/agent/runtime/lifecycle ./internal/ssh ./internal/launcher ./internal/system/metrics) already cover this.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge — all prior blocker and suggestion findings from this PR's review rounds are fixed, tested, and consistent across backend, desktop, release pipeline, and Docker packaging.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2946a1676b

ℹ️ 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".

Comment thread apps/backend/internal/agent/runtime/lifecycle/executor_ssh.go Outdated
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 1m 32s —— View job


Review

  • Gather context (PR diff, prior review rounds, latest commit)
  • Review latest commit (295b5d4 — align ssh readiness shell defaults) in detail
  • Review tests and verification
  • Post findings

This pass focuses on 295b5d4 ("fix: align ssh readiness shell defaults"), which addresses the Codex P2 finding that the SSH readiness probe/UI defaulted to bash even on Darwin hosts, diverging from the zsh default used by actual launches.

Verified

  • SSHDefaultShellForPlatform (apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go:368-374) centralizes the Darwin→zsh/else→bash choice; sshShellForRemote (executor_ssh.go:679-684) now delegates to it instead of duplicating the branch — single source of truth for launch and readiness paths.
  • probeAgents/probeShells (apps/backend/internal/ssh/handlers.go:267-272, 360-388) now probe the remote platform via SSHProbeRemote and resolve the effective shell through readinessShellForRequest, which trims an explicit override and otherwise falls through to the platform default. The response surfaces Shell/DefaultShell/Platform so the UI and the actual probe never disagree.
  • Frontend (ssh-agent-readiness-card.tsx): shell state now starts unset ("") rather than hard-coded "bash", with defaultShell tracked separately and updated from either probe response; readinessDisplayShell/readinessProbeBody are extracted as pure, directly-tested helpers (ssh-agent-readiness-card.test.ts). An explicit user choice still overrides the detected default, and selecting a value in the dropdown persists it as an explicit ssh_shell via onShellChange, consistent with the existing profile-metadata flow.
  • New backend test TestReadinessShellForRequest (handlers_test.go) covers explicit/trimmed shell, Darwin default, Linux default, and the zero-value-platform default — matches the implementation precisely.
  • apps/cli/src/release-config.test.ts change is just adjusting the expected workflow string for the already-landed Darwin signing-loop fix; unrelated to this commit's main change but harmless.

Findings

No blockers or new suggestions. The extra SSHProbeRemote round trip added to probeAgents/probeShells is a small additional SSH round trip per manual "Probe agents"/shell-probe click, but both are already multi-round-trip, user-initiated, non-polling operations, so the added cost is negligible.

This closes out the last open item (Codex P2: readiness defaults diverging from launch defaults) from earlier review rounds. Combined with the fixes verified in prior passes (platform-normalization blocker, resolver test coverage, host-fallback comment, Darwin Docker helpers, Darwin codesigning), I found no remaining issues across the full diff.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge.

Comment thread apps/web/lib/types/http-ssh.ts Outdated
Comment thread apps/web/components/settings/ssh-agent-readiness-card.tsx
@github-actions

Copy link
Copy Markdown
Contributor

OpenCode findings that could not be posted inline

OpenCode model: opencode-go/minimax-m3
Commit: 295b5d4a7c45

Some findings could not be posted inline, so they are preserved here.

Inline placement unavailable

apps/backend/internal/ssh/handlers.go:246 - Shell parameter from probe-agents body is concatenated into a remote command without validation

httpProbeAgents reads body.Shell (line 246) and passes it through readinessShellForRequest (which only strings.TrimSpaces it) to lifecycle.ProbeRemoteBinaryWrapLoginShell (executor_ssh_operations.go:386-391), which builds <shell> -lc '<cmd>' by raw string concatenation — the shell token is never shell-quoted. A payload like {"shell":"bash; rm -rf /"} therefore produces bash; rm -rf / -lc 'command -v …' on the remote. The user already controls their SSH host, so the realistic blast radius is self-inflicted, but the AGENTS.md guidance is "agent/tool execution is constrained by code, not prompt text alone". Add a validate step (e.g. a small validateShell(string) error that requires ^[a-zA-Z0-9_./-]+$ and rejects the value on failure, returning 400) in readinessShellForRequest or httpProbeAgents. The same gap exists for the profile-saved MetadataKeySSHShell consumed by sshShellFromMetadataWrapLoginShell, so a centralized validator in the lifecycle package would cover both call sites.

@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 1m 49s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Findings

Reviewed the latest commit 86726da ("clean up ssh readiness defaults"), which closes out the two OpenCode findings from the previous round on 295b5d4:

  • ProbeShellsResponse.Platform field removed (apps/backend/internal/ssh/handlers.go, apps/web/lib/types/http-ssh.ts) — confirmed it was genuinely unused (only default_shell/available were read by the readiness card and e2e tests), and probeShells still computes platform internally to derive DefaultShell via readinessShellForRequest, so no functional loss.
  • defaultShell reset on executor change (apps/web/components/settings/ssh-agent-readiness-card.tsx:106) — setDefaultShell(DEFAULT_SHELL) now sits in the same reset block as the other setX calls, so switching executors no longer shows a stale platform default while the new probe is in flight.

No leftover references to the removed Platform field in handlers_test.go or the e2e specs — checked both.

One pre-existing item, not introduced by this commit, for the record: OpenCode's earlier comment about body.Shell flowing unvalidated into WrapLoginShell's string-concatenated shell -lc '<cmd>' (apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go:386) is still technically true, but that code path (ProbeAgentsRequest.ShellprobeAgents) predates this PR on origin/main — it's not a regression from this branch, so not re-flagging as a blocker here.

No new blockers or suggestions from this commit.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge.

@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

🧹 Nitpick comments (2)
scripts/release/verify-desktop-runtime.sh (1)

71-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid duplicating helper names in the Windows allowlist.

Line 71 hardcodes the same helper set that REMOTE_AGENTCTL_HELPERS already defines. That creates an easy drift path where a new helper is required at Lines 118-122 but still rejected on Windows here.

🤖 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 `@scripts/release/verify-desktop-runtime.sh` at line 71, The Windows allowlist
in verify-desktop-runtime currently duplicates the helper names already defined
by REMOTE_AGENTCTL_HELPERS, which can drift out of sync. Update the allowlist
logic in verify-desktop-runtime.sh so it reuses the existing
REMOTE_AGENTCTL_HELPERS source of truth instead of hardcoding agentctl helper
names, and ensure the Windows case accepts any helper added there without
needing a second manual update.
apps/web/app/settings/executors/[profileId]/page.tsx (1)

512-530: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the SSH-only header action from ProfileEditForm.

ProfileEditForm now spans Lines 511-617, which crosses the repo’s 100-line function cap. This new SSH-only header-action block is a clean extraction point and would keep the form body focused on state/persistence wiring. As per coding guidelines, apps/web/**/*.{ts,tsx,js,jsx} enforces Functions ≤100 lines and says to extract a helper function, custom hook, or sub-component when hitting code-quality limits.

♻️ Possible extraction
+function SSHConnectionSettingsAction({ executorId }: { executorId: string }) {
+  const router = useRouter();
+
+  return (
+    <Button
+      variant="outline"
+      size="sm"
+      onClick={() => router.push(`/settings/executors/ssh/${executorId}`)}
+      className="w-full cursor-pointer sm:w-auto"
+      data-testid="ssh-connection-settings-link"
+    >
+      <IconShieldLock className="mr-1.5 h-4 w-4" />
+      Connection Settings
+    </Button>
+  );
+}
+
 function ProfileEditForm({ executor, profile }: { executor: Executor; profile: ExecutorProfile }) {
-  const router = useRouter();
   const { items: secrets } = useSecrets();
   const persistence = useProfilePersistence(executor, profile);
   const form = useProfileFormState(executor, profile);
   const relatedContainers = useDockerProfileContainers(profile.id, form.isDocker);
   const spritesTokenMissing = form.isSprites && !form.spritesSecretId;
   const headerActions =
-    executor.type === "ssh" ? (
-      <Button
-        variant="outline"
-        size="sm"
-        onClick={() => router.push(`/settings/executors/ssh/${executor.id}`)}
-        className="w-full cursor-pointer sm:w-auto"
-        data-testid="ssh-connection-settings-link"
-      >
-        <IconShieldLock className="mr-1.5 h-4 w-4" />
-        Connection Settings
-      </Button>
-    ) : undefined;
+    executor.type === "ssh" ? <SSHConnectionSettingsAction executorId={executor.id} /> : undefined;
🤖 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/web/app/settings/executors/`[profileId]/page.tsx around lines 512 - 530,
`ProfileEditForm` is over the function-length limit because the SSH-only
`headerActions` block is embedded directly in the form body. Extract that
`executor.type === "ssh"` button logic into a small helper function, custom
hook, or sub-component near `ProfileEditForm` so the main form stays focused on
state and persistence wiring. Keep the existing `useRouter`, `executor`, and
`headerActions` behavior unchanged, but move the SSH-specific JSX behind a
clearly named extraction referenced from `ProfileEditForm`.

Source: Coding guidelines

🤖 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/web/components/settings/ssh-agent-readiness-card.tsx`:
- Around line 28-34: Normalize the SSH shell value once and reuse it everywhere
in ssh-agent-readiness-card.tsx: readinessProbeBody already trims
whitespace-only input, but readinessDisplayShell and the branches around the
selector/default handling still use the raw shell string. Update the logic
around readinessProbeBody, readinessDisplayShell, and the related state/default
selection path so whitespace-only ssh_shell values are treated as empty for both
the probe request and the UI/display fallback, ensuring the detected default
shell is adopted consistently.

In `@scripts/release/prepare-desktop-runtime.sh`:
- Around line 116-119: The helper copy loop in prepare-desktop-runtime.sh
bypasses the script’s standard missing-file handling, so replace the direct
cp/chmod logic in the REMOTE_AGENTCTL_HELPERS loop with copy_one() and keep
using the existing copy_one() error path and symbols so missing helpers fail
with the same “Missing … in …” message as other assets.

---

Nitpick comments:
In `@apps/web/app/settings/executors/`[profileId]/page.tsx:
- Around line 512-530: `ProfileEditForm` is over the function-length limit
because the SSH-only `headerActions` block is embedded directly in the form
body. Extract that `executor.type === "ssh"` button logic into a small helper
function, custom hook, or sub-component near `ProfileEditForm` so the main form
stays focused on state and persistence wiring. Keep the existing `useRouter`,
`executor`, and `headerActions` behavior unchanged, but move the SSH-specific
JSX behind a clearly named extraction referenced from `ProfileEditForm`.

In `@scripts/release/verify-desktop-runtime.sh`:
- Line 71: The Windows allowlist in verify-desktop-runtime currently duplicates
the helper names already defined by REMOTE_AGENTCTL_HELPERS, which can drift out
of sync. Update the allowlist logic in verify-desktop-runtime.sh so it reuses
the existing REMOTE_AGENTCTL_HELPERS source of truth instead of hardcoding
agentctl helper names, and ensure the Windows case accepts any helper added
there without needing a second manual update.
🪄 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: c4e3f7ea-1e54-436d-a081-b3ea05668824

📥 Commits

Reviewing files that changed from the base of the PR and between 88b5710 and 295b5d4.

📒 Files selected for processing (43)
  • .github/workflows/release.yml
  • Dockerfile
  • Makefile
  • apps/backend/Makefile
  • apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver.go
  • apps/backend/internal/agent/runtime/lifecycle/agentctl_resolver_test.go
  • apps/backend/internal/agent/runtime/lifecycle/executor_ssh.go
  • apps/backend/internal/agent/runtime/lifecycle/executor_ssh_connection_test.go
  • apps/backend/internal/agent/runtime/lifecycle/executor_ssh_credentials.go
  • apps/backend/internal/agent/runtime/lifecycle/executor_ssh_credentials_test.go
  • apps/backend/internal/agent/runtime/lifecycle/executor_ssh_operations.go
  • apps/backend/internal/launcher/bundle.go
  • apps/backend/internal/launcher/bundle_test.go
  • apps/backend/internal/ssh/handlers.go
  • apps/backend/internal/ssh/handlers_test.go
  • apps/backend/internal/system/metrics/collector_darwin_nocgo.go
  • apps/cli/README_internal.md
  • apps/cli/src/release-config.test.ts
  • apps/desktop/AGENTS.md
  • apps/desktop/e2e/desktop-launch-smoke.mjs
  • apps/desktop/src-tauri/src/backend.rs
  • apps/web/app/settings/executors/[profileId]/page.tsx
  • apps/web/app/settings/executors/new/[type]/ssh-create-page.tsx
  • apps/web/components/settings/profile-edit/profile-edit-page-chrome.tsx
  • apps/web/components/settings/ssh-agent-readiness-card.test.ts
  • apps/web/components/settings/ssh-agent-readiness-card.tsx
  • apps/web/components/settings/ssh-connection-card.tsx
  • apps/web/e2e/tests/settings/mobile-ssh-profile-connection-link.spec.ts
  • apps/web/e2e/tests/settings/ssh-profile-connection-link.spec.ts
  • apps/web/e2e/tests/ssh/error-surfacing.spec.ts
  • apps/web/e2e/tests/ssh/test-endpoint.spec.ts
  • apps/web/e2e/tests/ssh/test-result.spec.ts
  • apps/web/lib/types/http-ssh.ts
  • docs/plans/desktop-tauri-app/plan.md
  • docs/plans/desktop-tauri-app/task-03-desktop-runtime-resources.md
  • docs/specs/native-kandev-cli/plan.md
  • docs/specs/native-kandev-cli/spec.md
  • docs/specs/ssh-executor/e2e-plan.md
  • docs/specs/ssh-executor/spec.md
  • scripts/release-desktop.test.sh
  • scripts/release/package-bundle.sh
  • scripts/release/prepare-desktop-runtime.sh
  • scripts/release/verify-desktop-runtime.sh

Comment thread apps/web/components/settings/ssh-agent-readiness-card.tsx Outdated
Comment thread scripts/release/prepare-desktop-runtime.sh
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 49s —— View job


Code Review

  • Identify changed files and check scope (this pass: 5d859cc, the latest push — diff against the prior reviewed commit 295b5d4)
  • Review tests and verification
  • Review implementation
  • Post findings

This commit ("fix: address ssh readiness review followups") is a small, targeted diff closing out the two OpenCode findings from the previous round:

  • apps/backend/internal/ssh/handlers.go / apps/web/lib/types/http-ssh.ts: dropped the unused Platform/platform field from ProbeShellsResponse — confirmed it's genuinely unused (no remaining reference to it in handlers_test.go, the readiness card, or e2e specs); probeShells still computes platform internally to derive DefaultShell via readinessShellForRequest, so no functional regression.
  • apps/web/components/settings/ssh-agent-readiness-card.tsx: extracted normalizeShell (trims) and reused it in both readinessProbeBody and readinessDisplayShell, so whitespace-only ssh_shell values are now treated consistently as "no explicit shell" in both the probe request and the display fallback. refresh() now checks !probeBody.shell (the normalized value) instead of the raw shell state before adopting the detected default — fixes the bug where a whitespace-only saved shell would previously skip adopting resp.shell. New test case (ssh-agent-readiness-card.test.ts) pins readinessDisplayShell(" ", "zsh") === "zsh".
  • Same file: setDefaultShell(DEFAULT_SHELL) added to the executor-switch reset block, so the selector placeholder can no longer show a stale platform default from the previously-selected executor while the new probe is in flight.
  • scripts/release/prepare-desktop-runtime.sh: the REMOTE_AGENTCTL_HELPERS copy loop now calls the script's existing copy_one() helper instead of raw cp/chmod. Verified copy_one (lines 99-111) resolves $BUNDLE_DIR/bin/$helper correctly via its variadic source loop and basename, and now fails with the same Missing <label> in <dir> message on a missing helper instead of a raw cp: No such file error — consistent with the other two copy_one calls in the file.

Findings

No new issues. All four changes are correctly scoped fixes for previously-flagged review comments (the OpenCode unused-field finding, the OpenCode stale-defaultShell finding, the CodeRabbit whitespace-normalization finding, and the CodeRabbit copy_one finding), each verified against the current code rather than taken on faith.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge.

@carlosflorencio carlosflorencio merged commit f3b26db into main Jun 29, 2026
67 checks passed
@carlosflorencio carlosflorencio deleted the feature/discord-general-darw-7wh branch June 29, 2026 18:06
@iamcobolt

Copy link
Copy Markdown

Review: live validation on real Apple Silicon Macs

Validated this change end-to-end against real Apple Silicon Macs over Tailscale (not loopback) — cross-built + ad-hoc-signed the darwin agentctl on a Linux control plane and drove the SSH executor: probe → platform gate → upload → launch → bind → real agent task. The core macOS support works. Along the way I hit two genuine bugs and one scope regression; details + suggested fixes below (I have patches for all three).

🔴 Bug 1 — env-authenticated agents can't authenticate over SSH (blocks Claude tasks)

Every Claude-ACP task on an SSH remote fails with {"code":-32000,"message":"Authentication required"}. Two root causes:

  1. Credential resolution is gated to containers. applyContainerCredentials (which resolves remote_credentials/remote_auth_secrets into req.Env, e.g. CLAUDE_CODE_OAUTH_TOKEN) runs only when isContainerizedExecutor is true — {LocalDocker, RemoteDocker, Sprites}. SSH is excluded, so req.Env never gets the token.
  2. createRemoteAgentInstance never sets CreateInstanceRequest.Env. Docker uses Env: req.Env; agentctl feeds it to the agent via CollectAgentEnv(req.Env) (instance/manager.go). SSH left it unset, so even a resolved env wouldn't reach the remote agent.

The adapter authenticates from the CLAUDE_CODE_OAUTH_TOKEN env var, not a credentials file — confirmed with a direct ACP probe: valid token in env → stop_reason: end_turn; absent/expired → 401 authentication_failed. File-type creds (codex/opencode auth.json) get uploaded and work; env-only agents get nothing.

Suggested fix: run credential resolution for SSH (drop the container-only gating, or resolve env-type auth methods in the SSH executor) and forward the resolved credential env into CreateInstanceRequest.Env — scoped to a credential allowlist, never the control plane's HOME/PATH. (My patch implements the forwarding + allowlist; the gating change is the cleaner half.)

🔴 Bug 2 — terminals / workspace-restore broken on SSH sessions

Opening a terminal or restoring a finished SSH task throws ssh executor: host (or host_alias) is required in executor config. The agent-launch path projects the executor record's SSH config into metadata, but GetWorkspaceInfoForSession copies only info.ExecutorType = string(exec.Type) and drops exec.Config (ssh_host, fingerprint, user). Those keys only survive via a live ExecutorRunning record (mergePersistentWorkspaceMetadata), so the moment the session is terminal-state (completed/failed), post-restart, or post-cleanup, the terminal/restore path fails.

Suggested fix: project the executor record's persistent SSH config into the workspace metadata as a fallback when no running record exists (non-overwriting, so a live record still wins).

🟡 Regression — linux/arm64 support dropped + darwin signing left implicit

  • The platform gate (requireSupportedRemotePlatform), build-agentctl-remote, requiredAgentctlRemoteHelpers, and the desktop validators (backend.rs, verify-/prepare-desktop-runtime.sh, desktop-launch-smoke.mjs) all omit linux/arm64, which the predecessor branch supported — ARM Linux remotes (Graviton, etc.) now get rejected. Intentional macOS-only scoping? If not, worth restoring.
  • Go's linker ad-hoc-signs darwin/arm64 at link time but not darwin/amd64 (verified empirically). Worth making the ad-hoc signing explicit and having bundle validation assert agentctl-darwin-arm64 carries an LC_CODE_SIGNATURE — an unsigned arm64 helper is SIGKILLed on every Apple Silicon host and would otherwise regress silently.

✅ Verified working

darwin/arm64 build + ad-hoc sign on Linux; platform gate accepts Darwin/arm64; signed agentctl uploads, launches, and binds (HTTP server bound successfully) on two Apple Silicon Macs; a full Claude task completes once credentials reach the agent.

Not bugs (noted to save reviewer time)

  • host_fingerprint is required on an untrusted executor is the trust-on-first-use gate working as designed — just needs Test Connection + trust. Minor: a docs/UI hint that a new SSH executor must be trusted before its first task would stop it reading as a failure.
  • The macOS host needs Remote Login + key auth; a Tailscale-SSH-ACL'd host is blocked independent of this PR.

iamcobolt added a commit to iamcobolt/kandev that referenced this pull request Jun 30, 2026
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>
@iamcobolt

Copy link
Copy Markdown

Opened #1559 with fixes for the two 🔴 bugs (env-auth creds not forwarded to remote SSH agents; terminal/workspace-restore missing host config) and the 🟡 linux/arm64 regression noted above — each with a unit test, validated live on Apple Silicon.

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.

2 participants