Ask who a new login connects as: your agent or yourself
What
At a terminal, against Basecamp's own authorization server, basecamp auth login now asks:
Connect this computer as
> Your agent (recommended)
Yourself
Enter picks your agent. That runs the existing agent-connect ceremony, through the same connectAgentProfile code auth agent connect uses. Its approval page offers Create my agent (now with name and avatar, basecamp/bc3#14083), my agent, or a team agent. Choosing yourself runs today's device-flow login. --as agent|me answers ahead of time (strict values; --agent is taken by the output mode).
This is v1 of "Connect as" from basecamp/bc3#14079, doc/oauth/connect_as.md §4: CLI-only, no server change, no SDK change.
Why this shape
The server can't safely send the login's device poll into the agent handover. An old CLI would either fail the poll after the code is spent, or save an agent's token as a person's and drop the secret. The device flow also sends no CLI version. So the choice is made in the CLI, before either flow starts.
Behaviour
| Situation | What happens |
|---|---|
Terminal (stdin and stderr are TTYs), BC5 issuer, no --as | Asks; Enter = agent |
No terminal, BASECAMP_NONINTERACTIVE, machine output (flag or configured) | Unchanged: signs you in, asks nothing |
--as me | Today's login, never asks |
--as agent | Ceremony, terminal or not; --as agent --json keeps the ceremony's two-value stdout contract |
| Launchpad (non-BC5) issuer | Never asks; --as agent is refused with the ceremony's existing "no agent grant" error |
--login-hint / --expect-identity without --as | Count as "yourself"; nothing asked |
--login-hint / --expect-identity with --as agent | Refused before any request (they only apply to a person) |
--with-token / --with-client-credentials with --as | Refused by cobra as mutually exclusive |
| Re-signing a profile (or top-level credential) that already holds your own login | Never asks: that's signing back in, not a new connection, so the auth login -P <p> remedies elsewhere still do what they say |
Profile for the agent: the -P/BASECAMP_PROFILE you name, else the active profile if it already holds an agent, else agent (created if needed). This is the guided setup's rule (guidedSetupProfile), reused. A profile holding a person's login is refused, never converted. Reconnecting an agent profile says that approving replaces its credential. A new agent profile becomes the default only when it's the only profile (registerProfile's existing rule). Otherwise the login prints basecamp profile set-default agent.
No agent access: bc3's page shows "Connecting agents isn't available yet". Its "OK, got it" denies the grant, so the poll answers access_denied, the same as an explicit Decline. The CLI can't tell the two apart. An intake 503 (not issuing) is the other no-agent ending. Both are now marked auth.ErrNoAgentConnection. Through login, the message is "No agent was connected: the connection was declined, or agents aren't available to you yet". At a terminal it then asks "Connect as yourself instead?" (Enter = yes) and runs the person's login under the profile it would have used without the detour. Without a terminal it exits non-zero (auth) with the hint basecamp auth login --as me. An expired code is not treated as no-agent.
Discovery runs once. To know whether to ask, the login has to discover first. auth.Provider / DiscoverProvider pass that result to Login or ConnectAgent so they don't discover again. The non-interactive path doesn't pre-discover at all; Login discovers as before.
Compatibility
- Non-interactive runs (scripts, CI, no TTY,
--json/--agent/configured json) behave exactly as before: a person login, no prompt, no extra request.TestAuthLoginWithoutATerminalSignsYouInWithoutAskingruns the real gate with stdin as a pipe. It asserts nothing is asked, no intake request is made, discovery runs once, and the stored credential isbc5. - No wire change: the same device-flow and agent-connection requests as today. Works against today's production bc3, with no server dependency.
auth agent connectis unchanged apart from its help text.
Tests
internal/commands/auth_login_connect_as_test.go: 19 tests (+6 subtests), against one mock Basecamp serving both the device flow and the ceremony. Covered:- Enter default (agent offered first, plus a tui test that a select's Enter picks its first option)
--asparsing and validation- the gate (pipe stdin,
BASECAMP_NONINTERACTIVE,--json, configured json) - the non-TTY path unchanged
- prompt → agent (one discovery, no device flow,
agentprofile becomes default) - prompt → yourself
--asboth ways, at and off a terminal- person-only flags
- re-signing your own profile asks nothing
- a person-login profile is refused for the agent
- reconnect notice, set-default hint
- Launchpad
- no-agent fallback (access_denied and 503) → yes / no / non-interactive
- cancel at the question
--as agent --jsoncontract
internal/auth: decline and 503 carryErrNoAgentConnection; an expired code does not.internal/tui:TestSelectFieldAnsweredWithEnterIsItsFirstOption.- Full
bin/ci(make check) on 40991a03: exit 0. 5,228 top-level Go tests pass, 0 fail, 6 skipped. 510/510 bats e2e. Lint, surface (+2--asentries), skill drift, hint-command resolution and tidy all pass. Run on a Linux host: this Mac session can't open ptys, so the terminal-predicate tests fail here on any branch.
Docs: README (Authentication; agent section), install.md, auth login and auth agent connect help, basecamp-connect and basecamp-doctor skills.
Refs basecamp/bc3#14079, basecamp/bc3#14083.
Hold: do not merge.
Summary by cubic
basecamp auth login now asks, at a terminal against Basecamp's own server, whether this computer connects as your agent or as yourself. The choice is made in the CLI before either flow starts: the server can't safely send the login's device poll into the agent handover, and the device flow carries no CLI version to tell old from new.
Behavior
- Choosing your agent runs the same approval ceremony as
auth agent connect, under the-Pprofile, else a profile namedagent. --as agentor--as meanswers ahead of time;--as agent --jsonkeeps the ceremony's two-value stdout contract.- When no agent connects — declined, or the intake answers 503 (a poll answered 503 backs off instead, and isn't counted) — a terminal login offers to connect you instead; off a terminal it fails with a hint. The offer is skipped where the person's login couldn't land: the ceremony ran on another Basecamp (this process's OAuth client is fixed to that profile's), the agent being reconnected is the profile the login started on (the person's login would replace its credential), or the login started on a profile that doesn't exist.
- Re-signing a profile that already holds your own login, and
--login-hintor--expect-identity, skip the question; person-only flags are refused with--as agent. - At a terminal,
auth login -P <new>asks before refusing the missing profile: the agent creates it, yourself still needs it to exist. - Asking means discovering first, which fixes the OAuth client to the base URL the login started on; an agent profile bound to another Basecamp is refused before anything is requested, naming the command that starts there.
- Where this computer's hostname can't be read, the agent path refuses before anything is requested and names
auth agent connect --device-name, sinceauth loginhas no--device-name.
Compatibility
- Non-interactive runs — no TTY, scripts, CI,
BASECAMP_NONINTERACTIVE, machine output — sign a person in exactly as before, with no extra request. - Launchpad is unchanged: it never asks, and
--as agentis refused with the ceremony's existing "no agent grant" error. - Discovery runs once: the login hands the discovered provider to
LoginorConnectAgentinstead of discovering again.