feat(hetzner): faster git push/pull via ssh -A + credential proxy#4
feat(hetzner): faster git push/pull via ssh -A + credential proxy#4madarco wants to merge 2 commits into
Conversation
Replaces the bundle round-trip on Hetzner with a single ephemeral ssh -A exec per RPC. For SSH origins the in-box git uses the host's forwarded ssh-agent; for HTTPS origins a short-lived host-loopback credential proxy is exposed via ssh -R for the duration of the push. Forwarded agent socket / reverse-forwarded port disappear with the SSH session — no persistent credential exposure inside the box. Falls back to the existing bundle path on missing host agent, auth failure, or non-SSH/HTTPS origins. askPrompt() gate is preserved. Also adds `agentbox git box-fetch <box> [refspec...]` (Hetzner only) so the host can pull a box's commits over SSH into refs/remotes/agentbox-<id>/* without registering a persistent remote.
- relay/host-actions: split `isFastPathAuthFailure` per scheme. SSH patterns (publickey denied / agent refused) only match SSH; HTTPS matches "could not read username" / "terminal prompts disabled" / "credential helper exited with" so HTTPS auth failures now fall back to the bundle path instead of returning success-with-error. Real upstream 401s still propagate (the bundle path can't fix those). - apps/cli/git.ts: shell-quote `target.identity` and `target.knownHosts` in the GIT_SSH_COMMAND string so paths with spaces (\$HOME like "/Users/Foo Bar/…") don't break ssh's argv parsing. - apps/cli/git.ts: drop unused `BoxFetchOptions` interface.
|
bugbot review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 30b2971. Configure here.
| stderr.includes('could not read username') || | ||
| stderr.includes('terminal prompts disabled') || | ||
| stderr.includes('credential helper') && stderr.includes('exited with') | ||
| ); |
There was a problem hiding this comment.
Missing parentheses cause ambiguous operator precedence
Medium Severity
The isFastPathAuthFailure return expression mixes || and && without explicit parentheses: A || B || C && D. JavaScript's && binding tighter than || makes this evaluate as A || B || (C && D), which happens to match the intended behavior. However, this is a well-known readability trap — a future maintainer adding a condition or reordering terms could easily introduce a real logic bug without noticing the implicit grouping. Adding explicit parentheses around the && clause makes the intent unambiguous.
Reviewed by Cursor Bugbot for commit 30b2971. Configure here.
| function classifyRemoteUrl(url: string): RemoteScheme { | ||
| if (/^ssh:\/\//i.test(url)) return 'ssh'; | ||
| // scp-like: user@host:path/to/repo.git | ||
| if (/^[^/@\s]+@[^/:\s]+:[^/]/.test(url)) return 'ssh'; |
There was a problem hiding this comment.
SCP URLs with absolute paths misclassified as non-SSH
Low Severity
The classifyRemoteUrl regex for SCP-like SSH URLs uses [^/] after the colon, rejecting valid URLs like git@host:/absolute/path/to/repo.git. Git treats user@host:/path as a valid SCP-style SSH remote, but this pattern falls through to 'other', causing the fast path to be skipped unnecessarily for users with self-hosted Git servers that use absolute paths. The fallback to the bundle path still works, but the optimization is silently lost.
Reviewed by Cursor Bugbot for commit 30b2971. Configure here.
…tems The 9 actionable items are done. Write concrete implementation plans (from this session's investigation) into the backlog for the heavier/interactive remainder so they can be picked up on the host: - #4 relay round-trip: why nested doesn't work + the host runbook (E2E_RELAY=1) - #8 ttyd attach: bake ttyd + 4th port + ws client rewrite (needs a re-bake) - #14 per-project snapshot tier: prepared-state projects[<hash>] mirroring daytona/hetzner - #16 Sandbox.fork: SDK is ready; needs a finalizeCloudBox refactor of the shared create() + an `agentbox fork` command + a branch-semantics decision
…el AL2023 Found while verifying the git-shim-ordering fix: in a booted vercel box `command -v git` resolves to /opt/git/bin/git (Vercel prepends /opt/git/bin ahead of /usr/local/bin), so the relay-routing shims at /usr/local/bin are inert at runtime — agent-initiated `git push`/`gh pr` hit the real binaries instead of routing through the relay. Document the finding + a host fix plan (prepend /usr/local/bin in the login-shell profile.d shim). Tied to #4.
The old Phase D trusted the 'agentbox shell ... git push' exit code, but on vercel 'shell' goes through the laggy attach pump whose exit code reflects the attach wrapper, not the in-box command — yielding a false PASS on 2026-05-29 (commit never reached origin). Phase D now gates on ground truth: poll origin for the box branch 'agentbox/<box>' appearing after the push (it's absent before). Doc: revert #4 to unconfirmed and record the false-positive analysis.
Verified end-to-end on box relayv1: in-box agentbox-ctl git push of commit fc6d54de reached origin (git ls-remote confirmed) via the bridge/poller/runGitRpc chain. Root cause of prior failures was a stale host-relay process lacking the vercel executor, plus the secrets.env/chunk/PATH fixes already committed. Records the stale-relay gotcha + ensureRelay follow-up.


Replaces the bundle round-trip on Hetzner with a single ephemeral ssh -A exec per RPC. For SSH origins the in-box git uses the host's forwarded ssh-agent; for HTTPS origins a short-lived host-loopback credential proxy is exposed via ssh -R for the duration of the push. Forwarded agent socket / reverse-forwarded port disappear with the SSH session — no persistent credential exposure inside the box. Falls back to the existing bundle path on missing host agent, auth failure, or non-SSH/HTTPS origins. askPrompt() gate is preserved.
Also adds
agentbox git box-fetch <box> [refspec...](Hetzner only) so the host can pull a box's commits over SSH into refs/remotes/agentbox-/* without registering a persistent remote.Note
High Risk
Changes how cloud git RPC authenticates (SSH agent forwarding and HTTPS credentials tunneled into the box); mitigated by session-scoped forwarding, loopback-only proxy, and bundle fallback, but still security-sensitive.
Overview
On Hetzner, relay
git.push/git.fetchnow try a fast path before the existing git-bundle flow: a one-offssh -Arunsgit push/git fetchinside the box againstorigin. SSH remotes use the host’s forwarded agent; HTTPS remotes use a short-lived host loopback credential proxy (host-credential-proxy.ts) exposed into the box viassh -R, with in-box git usingncas a credential helper.CloudBackend.execWithAgent(optional, withreverseForward) is implemented on Hetzner viasshExecWithAgent. Any transport/auth failure falls back to the bundle path; theaskPrompt()gate is unchanged.Adds
agentbox git box-fetch <box> [refspec...](Hetzner only): hostgit fetchover per-box SSH intorefs/remotes/agentbox-<box-id>/*with no persistent remote. Docs/backlog updated to describe both flows.Reviewed by Cursor Bugbot for commit 30b2971. Configure here.