Skip to content

feat(hetzner): faster git push/pull via ssh -A + credential proxy#4

Closed
madarco wants to merge 2 commits into
mainfrom
feat/hetzner-git-fast-path
Closed

feat(hetzner): faster git push/pull via ssh -A + credential proxy#4
madarco wants to merge 2 commits into
mainfrom
feat/hetzner-git-fast-path

Conversation

@madarco
Copy link
Copy Markdown
Owner

@madarco madarco commented May 26, 2026

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.fetch now try a fast path before the existing git-bundle flow: a one-off ssh -A runs git push / git fetch inside the box against origin. 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 via ssh -R, with in-box git using nc as a credential helper. CloudBackend.execWithAgent (optional, with reverseForward) is implemented on Hetzner via sshExecWithAgent. Any transport/auth failure falls back to the bundle path; the askPrompt() gate is unchanged.

Adds agentbox git box-fetch <box> [refspec...] (Hetzner only): host git fetch over per-box SSH into refs/remotes/agentbox-<box-id>/* with no persistent remote. Docs/backlog updated to describe both flows.

Reviewed by Cursor Bugbot for commit 30b2971. Configure here.

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.
Comment thread packages/relay/src/host-actions.ts
Comment thread apps/cli/src/commands/git.ts
Comment thread apps/cli/src/commands/git.ts
- 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.
@madarco
Copy link
Copy Markdown
Owner Author

madarco commented May 26, 2026

bugbot review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 and found 2 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 30b2971. Configure here.

stderr.includes('could not read username') ||
stderr.includes('terminal prompts disabled') ||
stderr.includes('credential helper') && stderr.includes('exited with')
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 30b2971. Configure here.

@madarco madarco marked this pull request as draft May 27, 2026 07:07
@madarco madarco closed this May 27, 2026
@madarco madarco deleted the feat/hetzner-git-fast-path branch May 27, 2026 11:25
madarco added a commit that referenced this pull request May 29, 2026
…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
madarco added a commit that referenced this pull request May 29, 2026
…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.
madarco added a commit that referenced this pull request May 29, 2026
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.
madarco added a commit that referenced this pull request May 29, 2026
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.
madarco added a commit that referenced this pull request May 29, 2026
- Fix the recorded probe commit in #4 to the real pushed hash (7f8eea5d, was a
  wrong earlier-run value).
- Add backlog #21: ensureRelay should reclaim a relay lacking a provider
  executor (the stale-relay gotcha that masked #4), with a /healthz build-stamp
  plan. Cross-provider.
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