Skip to content

feat(cli): agentbox shell --ssh-config for Codex / Claude desktop SSH#118

Merged
madarco merged 2 commits into
nightlyfrom
ssh-config-external-apps
Jun 26, 2026
Merged

feat(cli): agentbox shell --ssh-config for Codex / Claude desktop SSH#118
madarco merged 2 commits into
nightlyfrom
ssh-config-external-apps

Conversation

@madarco

@madarco madarco commented Jun 26, 2026

Copy link
Copy Markdown
Owner

What

Adds an explicit way to connect external apps (the Codex app, Claude desktop) to a box over plain SSH.

agentbox shell <box> --ssh-config (+ --json) brings the box online, writes a ~/.ssh/config alias, and prints the alias, host, user, identity-file path, a ready ssh <box> command, the Codex deep link codex://settings/connections/ssh/add?name=<box>, and Claude-desktop instructions.

Details

  • Hetzner-only gate. Only providers with a persistent per-box identity file qualify (an external app connects later). Docker/Daytona/Vercel/E2B exit cleanly without writing config.
  • Shared helper cloud-ssh.ts — extracted the duplicated "bring online → buildAttachparseSshTarget → write alias" flow into resolveCloudSshTarget / ensureCloudSshAlias; reused from code and open.
  • Alias is now the box name (clean ssh <box> and Codex name= param). Added readAgentboxSshAlias and surfaced ssh alias / ssh identity rows in cloud status --inspect.
  • Documented in the agentbox-info and fork skills.

Verification

  • typecheck ✅, lint ✅, 628 tests ✅ (updated ssh-config.test.ts), plugin-skill bundle in sync ✅
  • E2E on a real Hetzner box: --ssh-config wrote the alias with the real IP + identity file; ssh <box> connected as vscode; --json and status --inspect correct; docker box hit the negative gate with no config write. Boxes torn down after.

https://claude.ai/code/session_011VoAz7mUaUGh6dKAvr7kAP


Note

Medium Risk
Touches ~/.ssh/config and changes SSH Host alias naming (breaking for anyone relying on agentbox-cloud-*); Hetzner gating limits blast radius for external-app handoff.

Overview
Adds agentbox shell <box> --ssh-config (and --json) so Codex and Claude desktop can connect over SSH without opening an interactive shell. The command brings the box online, writes a managed Host <box-name> block in ~/.ssh/config, and prints connection details plus a Codex deep link.

Hetzner-only: resolution happens before any write; boxes without a persistent per-box identityFile (Docker, Daytona token auth, etc.) exit with a clear error and leave ~/.ssh/config unchanged.

Refactor: duplicated cloud SSH logic moves into cloud-ssh.ts (resolveCloudSshTarget / ensureCloudSshAlias), reused by agentbox code and agentbox open.

SSH aliases change from agentbox-cloud-<name> to the box name itself. readAgentboxSshAlias and new ssh alias / ssh identity rows on cloud inspect output. Host skills document the workflow.

Reviewed by Cursor Bugbot for commit 3ece428. Configure here.

…op SSH

Write a `~/.ssh/config` alias on demand so external apps (the Codex app,
Claude desktop) can connect to a box over plain SSH, and surface the
identity-file path + a Codex deep link.

- New `agentbox shell <box> --ssh-config` (+ `--json`): brings the box
  online, writes the alias, prints alias/host/user/identity + the
  `codex://settings/connections/ssh/add?name=<alias>` link and Claude
  desktop instructions. Gated to providers with a persistent per-box key
  (Hetzner) — Docker/Daytona/Vercel/E2B exit cleanly without writing.
- Extract the shared bring-online → buildAttach → parseSshTarget → write
  alias flow into `cloud-ssh.ts` (`resolveCloudSshTarget` /
  `ensureCloudSshAlias`); reuse it from `code` and `open`.
- Alias is now the box name (clean `ssh <box>` + Codex `name=` param);
  add `readAgentboxSshAlias` and surface `ssh alias` / `ssh identity` in
  cloud `inspect`.
- Document the flow in the agentbox-info and fork skills.

Claude-Session: https://claude.ai/code/session_011VoAz7mUaUGh6dKAvr7kAP
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
agentbox-web Skipped Skipped Jun 26, 2026 11:46am

Request Review

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3ece428. Configure here.

* leave `~/.ssh/config` untouched.
*/
async function runSshConfig(box: BoxRecord, opts: ShellOptions): Promise<void> {
const conn = await resolveCloudSshTarget(box);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Docker box started before failure

Medium Severity

runSshConfig always calls resolveCloudSshTarget, which resumes or starts the box before checking SSH support. On Docker boxes (no buildAttach), the command still unpause/start the container, then errors—contrary to the documented “exit cleanly without writing config” behavior for unsupported providers.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ece428. Configure here.

*/
export function agentboxAliasFor(boxName: string): string {
return `agentbox-cloud-${boxName}`;
return boxName;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SSH alias collides with user Host

Medium Severity

agentboxAliasFor now uses the bare box name as the Host alias. If the user already has a Host entry with that name in ~/.ssh/config, OpenSSH applies the first matching stanza per keyword, so the appended agentbox block may not supply the VPS HostName, key, or user—breaking ssh &lt;box&gt;, Codex, and Claude desktop handoff.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ece428. Configure here.

*/
export function agentboxAliasFor(boxName: string): string {
return `agentbox-cloud-${boxName}`;
return boxName;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Legacy SSH blocks not removed

Low Severity

Renaming the managed alias to the box name means stripBlock / removeAgentboxSshAlias only touch markers for the new alias. Existing agentbox-cloud-&lt;box&gt; blocks from earlier releases are never removed on rewrite or destroy, leaving stale Host entries and confusing SSH config.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ece428. Configure here.

Comment thread apps/cli/src/commands/code.ts Outdated
// Mint the SSH target and (re)write the `~/.ssh/config` alias. Shared with
// `agentbox open` and `agentbox shell --ssh-config`. The inner tmux command
// is irrelevant here (Remote-SSH starts its own session).
const { alias } = await ensureCloudSshAlias(box);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicate cloud lifecycle calls

Low Severity

prepareCloudAttach still runs pause/resume/start via probeState, then calls ensureCloudSshAlias, which runs the same lifecycle again inside resolveCloudSshTarget. The refactor duplicated bring-online logic instead of delegating once.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ece428. Configure here.

- Check SSH support (`buildAttach`) before any lifecycle action so an
  unsupported box (e.g. a stopped Docker box) errors without being
  started; add a `bringOnline` option to skip a redundant lifecycle pass.
- `agentbox code` now passes `bringOnline: false` (it already brings the
  box online + waits) — removes the duplicate resume/start.
- Migrate away legacy `agentbox-cloud-<box>` blocks on write/remove so the
  box-name rename doesn't leave stale Host entries behind.
- Warn (don't fail) when `~/.ssh/config` already has a user-authored
  `Host <box>` that could shadow agentbox's entry.
- Tests for legacy-block migration and conflict detection.

Claude-Session: https://claude.ai/code/session_011VoAz7mUaUGh6dKAvr7kAP
@madarco madarco merged commit e673517 into nightly Jun 26, 2026
3 checks passed
@madarco madarco deleted the ssh-config-external-apps branch June 26, 2026 11:49
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.

1 participant