Skip to content

Keep background remote refreshes from prompting for credentials - #2738

Open
noih wants to merge 1 commit into
get-bb:mainfrom
noih:fix/non-interactive-background-remote-refresh
Open

Keep background remote refreshes from prompting for credentials#2738
noih wants to merge 1 commit into
get-bb:mainfrom
noih:fix/non-interactive-background-remote-refresh

Conversation

@noih

@noih noih commented Aug 30, 2026

Copy link
Copy Markdown

Human comments

What was wrong

Opening the new-thread composer mounts useProjectSourceBranches, which requests GET /projects/:id/branches?refresh=background. The server turns that into host.inspect_git_source { remoteRefresh: "background" }, and the daemon starts the throttled git fetch --all --prune from #2616.

That fetch inherits whatever interactive git and ssh behaviour the daemon process happens to have. For a passphrase-protected key that is not loaded in an agent (or an https remote whose credential helper prompts), the background refresh cannot succeed, and upstream already treats it as best effort: the result is discarded and errors are swallowed. What varies is how it fails, and that depends on how the daemon was launched:

Daemon launched from Before After
Desktop app, no SSH_ASKPASS (no controlling tty; stdin is ignore) ssh cannot open /dev/tty, fails silently unchanged
A terminal (bb in the foreground) ssh reads the passphrase from that terminal's /dev/tty, git blocks until the 5s refresh timeout kills it fails immediately
SSH_ASKPASS in the daemon's environment (Linux desktops with an askpass helper, or a user-configured system-wide one) a GUI passphrase dialog appears on every composer visit, with no user action behind it fails silently
Keys loaded in ssh-agent / macOS Keychain (UseKeychain, AddKeysToAgent) succeeds unchanged

The third row is the reported case. The same principle #1608 applied to the browser's local-network prompt applies here: a background request must never surface a prompt; prompts belong to explicit user actions. A refresh that can pop a dialog is not a background refresh.

What changed

  • fetchRemoteBranches takes an explicit interactive flag. Non-interactive fetches run with GIT_TERMINAL_PROMPT=0 and the resolved ssh command (GIT_SSH_COMMAND, then core.sshCommand, then ssh) with -o BatchMode=yes appended, so user-configured keys, ports, and wrappers keep working.
  • The daemon's refreshRemoteBranches runs remoteRefresh: "background" non-interactively and remoteRefresh: "blocking" (branch picker open, bb project branches --refresh) interactively.
  • When a background refresh fails non-interactively, the next blocking refresh bypasses the 30-second throttle. Opening the picker right after a page load therefore still performs a real fetch, with a prompt if one is needed, instead of serving the refs the background attempt could not update.
  • No wire change: the host.inspect_git_source / host.list_branch_options contracts are untouched, so HOST_DAEMON_PROTOCOL_VERSION stays at 174. No CLI, guide, or app changes.

Users whose keys are loaded in an agent or Keychain see no behaviour change: the background fetch still succeeds silently, and the picker still opens against fresh refs.

Known limit

BatchMode only silences the ssh client. An agent that prompts on its own (a locked gnome-keyring key, ssh-add -c confirmation) still prompts, before and after this change. Silencing that would mean not contacting the agent at all, which would also break the common agent-backed case, so it is left as is.

Possible follow-up

The background attempt's failure is currently invisible. If that matters, the picker could show that remote refs may be stale until the user refreshes. Not included here to keep the change a behaviour fix.

How you verified

New tests use a recording core.sshCommand script against an ssh:// remote, so the assertions see exactly what git passes to ssh rather than a mock:

  • packages/host-workspace/test/git.test.ts: a non-interactive fetch passes -o BatchMode=yes and GIT_TERMINAL_PROMPT=0; an interactive fetch passes neither. The first case fails on main (expected '-G\n-o\nSendEnv=GIT_PROTOCOL\n…' to contain '-o\nBatchMode=yes\n').
  • apps/host-daemon/test/command/host-branches-dispatch.test.ts: remoteRefresh: "background" runs ssh with BatchMode=yes; remoteRefresh: "blocking" runs it without; a blocking refresh right after a failed background refresh performs a second, interactive fetch instead of being throttled.
  • pnpm exec turbo run test --filter=@bb/host-workspace --filter=@bb/host-daemon --force — host-workspace 230 passed, host-daemon passed. One pre-existing macOS failure (provisioning.test.ts › "runs teardown scripts before it removes managed worktrees", /private/tmp vs /tmp path comparison) fails identically on main.
  • pnpm exec turbo run typecheck lint --filter=@bb/host-workspace --filter=@bb/host-daemon — passed.

Fixes #2737

AGENT GENERATED

Opening the new-thread composer starts a throttled background
`git fetch --all --prune` through `host.inspect_git_source`. For a
passphrase-protected key that is not loaded in an agent, that fetch
cannot succeed and its result is already discarded, but how it failed
depended on how the daemon was launched: silently from the desktop app,
blocking on the terminal's `/dev/tty` until the 5s timeout when run from
a shell, or with a GUI passphrase dialog on every composer visit when
`SSH_ASKPASS` is set. None of those had a user action behind them.

Background refreshes now run non-interactively: `GIT_TERMINAL_PROMPT=0`
and the resolved ssh command (`GIT_SSH_COMMAND`, `core.sshCommand`, or
`ssh`) with `-o BatchMode=yes` appended. Blocking refreshes, which only
happen when the user opens the branch picker or passes
`bb project branches --refresh`, stay interactive.

When a background refresh fails non-interactively, the next blocking
refresh bypasses the 30-second throttle so the picker can still fetch
with a prompt instead of serving stale refs.
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.

Background remote refresh on the new-thread composer triggers an ssh passphrase prompt

1 participant