Skip to content

feat(open): add Claude desktop to the 'open in' apps (CLI + hub)#176

Merged
madarco merged 2 commits into
nightlyfrom
feat/open-in-claude
Jul 8, 2026
Merged

feat(open): add Claude desktop to the 'open in' apps (CLI + hub)#176
madarco merged 2 commits into
nightlyfrom
feat/open-in-claude

Conversation

@madarco

@madarco madarco commented Jul 8, 2026

Copy link
Copy Markdown
Owner

What

Adds claude to the open-in targets: agentbox open <box> --in claude registers the box in the Claude desktop app, and the hub's box-detail Apps row gains the matching launcher.

Claude has no codex-style add-SSH deep link, but it reads SSH environments from its own settings — ~/.claude/settings.jsonsshConfigs, where sshHost accepts an alias from ~/.ssh/config (schema verified against Claude.app v1.19367's bundle). Since AgentBox runs on the host, the CLI upserts the entry directly:

{ "id": "agentbox-<alias>", "name": "AgentBox: <box>", "sshHost": "<alias>", "startDirectory": "/workspace" }

then launches the app — the box appears in Claude's Environment dropdown, where the app can also list and resume the box's existing Claude sessions over SSH (it syncs remote transcripts). Using the alias (not raw host/port) keeps the entry evergreen: docker's ephemeral loopback sshd port changes across restarts, but syncAgentboxSshConfig keeps the alias fresh.

How

  • apps/cli/src/lib/claude-app-config.ts (new): id-keyed upsert into sshConfigs that preserves every foreign entry and settings key, and refuses to touch a settings file that doesn't parse (it's the user's real Claude Code settings). agentbox destroy prunes the box's entry (best-effort).
  • The persistent-SSH gate + alias write is factored out of openInCodex into ensurePersistentSshAlias, shared by both app targets (docker + hetzner boxes; same actionable errors for daytona/vercel/e2b and pre-sshd docker boxes).
  • Hub: claude added to OPEN_IN_APPS (validator), OpenInApp (backend types), and the Apps row (existing Icons.claude glyph). The probe/open plumbing shells the CLI, so no backend logic changed.
  • Docs: cli.mdx, access-your-box.mdx, docs/features.md.

Verification

  • 730 unit tests pass (new: claude target detection; upsert create/preserve/replace/corrupt-refuse/prune with per-file $HOME isolation so the suite never touches the real ~/.claude/settings.json).
  • Live on a hetzner box: open --in claude → before/after diff of the real settings.json is exactly one entry; double-open stays a single entry; ssh <alias> connects (the exact command Claude.app spawns); Claude.app launched with the box in the Environment dropdown.
  • Hub live: GET /api/v1/open-targets reports claude (docker, hetzner); POST /api/v1/boxes/:id/open {"app":"claude"}{"ok":true} and the app opened; invalid app rejected with the updated list.

Follow-up (separate repo): add Claude to the tray app's Open In… menu if it hardcodes the list.

https://claude.ai/code/session_0156d47n9hxNTCAuyjX7rEch


Note

Medium Risk
The CLI reads and writes the user's real ~/.claude/settings.json; safeguards limit blast radius but corrupt or unexpected schema could still block open/destroy cleanup until fixed manually.

Overview
Adds claude as a first-class agentbox open --in target alongside Codex, with the same docker + Hetzner persistent-SSH gating surfaced in open --targets and the hub/tray open-in lists.

open --in claude reuses a new shared ensurePersistentSshAlias (factored out of the Codex path) to sync the box SSH alias, then upserts an sshConfigs entry in ~/.claude/settings.json (no Codex-style deep link) and launches Claude.app so the box appears in the Environment dropdown. claude-app-config.ts owns read/merge/write by id, preserves unrelated settings, and refuses to modify invalid JSON.

agentbox destroy best-effort removes the matching agentbox-<alias> entry so stale SSH connections don’t linger. Hub/API types and the box Access → Apps row include Claude; docs updated.

Reviewed by Cursor Bugbot for commit 50b832e. Configure here.

'agentbox open <box> --in claude' registers the box in the Claude desktop
app. Claude has no codex-style add-SSH deep link, but it reads SSH
environments from its own settings — ~/.claude/settings.json sshConfigs,
where sshHost accepts an ~/.ssh/config alias — so the CLI upserts
{id: agentbox-<alias>, name: AgentBox: <box>, sshHost: <alias>,
startDirectory: /workspace} there directly (schema verified against
Claude.app v1.19367) and launches the app; the box appears in its
Environment dropdown, where the app can also list/resume the box's
existing Claude sessions over SSH.

The upsert (apps/cli/src/lib/claude-app-config.ts) matches by id,
preserves every foreign entry and settings key, and refuses to touch a
settings file that doesn't parse. 'agentbox destroy' prunes the box's
entry. The persistent-SSH gate + alias write is shared with codex via
ensurePersistentSshAlias (docker + hetzner boxes). The hub's box-detail
Apps row gains the Claude launcher (validator, backend types, UI entry).

Verified live on a hetzner box: settings diff is exactly one entry,
double-open stays a single entry, the alias connects over plain ssh, and
the hub POST /boxes/:id/open {app: claude} launches the app.

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

vercel Bot commented Jul 8, 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 Jul 8, 2026 3:34pm

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 1 potential issue.

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 50b832e. Configure here.

Comment thread apps/cli/src/commands/destroy.ts Outdated
}
} catch {
/* best-effort */
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hub destroy skips Claude cleanup

Medium Severity

removeClaudeSshConfigs runs only in the agentbox destroy CLI action. Hub lifecycle destroy (UI and POST …/destroy) and the dashboard destroy path call provider.destroy directly and never prune the box’s sshConfigs entry, so environments opened via the hub can remain in Claude after the box is gone.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 50b832e. Configure here.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 7b11a1a — good catch. The writer moved into @agentbox/sandbox-core and the pruning now runs inside syncAgentboxSshConfig as an agentbox-prefixed orphan sweep against live box names: every lifecycle path syncs the ssh config (docker destroyBox — which the hub's provider.destroy reaches — CLI cloud destroy, and any later create/start/open), so an entry left by a path that skipped the sync self-heals on the next one. Verified live: seeded a fake agentbox-ghost-box entry, triggered a sync, orphan swept while the live box's entry and foreign entries survived.

… CLI destroy

Bugbot: hub/dashboard destroys call provider.destroy directly and skipped
the CLI-destroy-only prune, leaving dead environments in Claude. Move the
writer into @agentbox/sandbox-core and sweep agentbox-prefixed orphans
inside syncAgentboxSshConfig — every lifecycle path syncs (docker
destroyBox, CLI cloud destroy, create/start), so an entry left by a path
that skipped the sync self-heals on the next one. Foreign entries are
never touched; a corrupt settings.json never breaks lifecycle (best-effort
there, still fail-loud on explicit open --in claude).

Claude-Session: https://claude.ai/code/session_0156d47n9hxNTCAuyjX7rEch
@madarco madarco merged commit 0801ac7 into nightly Jul 8, 2026
3 checks passed
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