Follow-on from the hang-family work (#652/#653/#654): basecamp login is the remaining interactive entry point with no machine-mode gate.
What happens
buildLoginCmd's RunE (internal/commands/auth.go:239) checks only JQFilter. Nothing in the login path consults IsInteractive, IsMachineOutput, or NonInteractiveEnv — grep the file, they don't appear.
So under --json / --agent / BASECAMP_NONINTERACTIVE=1:
- Launchpad flow: opens a browser (or prints a URL) and blocks on the OAuth callback with a 5-minute hard timeout (
internal/auth/auth.go:526,555).
- BC5 device flow: no CLI-side bound at all — the deadline is the server-issued device-code lifetime (typically 10–15 min), and the SDK's cap is ~24.8 days.
cmd.Context() is Background() with no signal.NotifyContext, so nothing local ever cancels it.
- Either way the caller gets prose on stdout, no envelope, and a process that sits there.
Repro
BASECAMP_NONINTERACTIVE=1 basecamp login --json </dev/null
Blocks for minutes instead of refusing with a structured error.
What already works
auth status (auth.go:48) is the well-behaved sibling: local-only, returns through app.OK, safe in any context.
Suggested shape
- Refuse up front under machine output or
NonInteractiveEnv, with a hint naming basecamp auth status (to check) and BASECAMP_TOKEN (to authenticate headlessly) — same pattern as the setup gate.
- If login is ever allowed to proceed under machine flags, reuse the envelope condition
auth token already has (app.Flags.JSON || app.Flags.Agent || app.Flags.JQFilter != "", auth.go:201) instead of writing prose to stdout.
Nearest prior issues (#183, #194, #528) are all closed and none covers this.
Follow-on from the hang-family work (#652/#653/#654):
basecamp loginis the remaining interactive entry point with no machine-mode gate.What happens
buildLoginCmd's RunE (internal/commands/auth.go:239) checks onlyJQFilter. Nothing in the login path consultsIsInteractive,IsMachineOutput, orNonInteractiveEnv— grep the file, they don't appear.So under
--json/--agent/BASECAMP_NONINTERACTIVE=1:internal/auth/auth.go:526,555).cmd.Context()isBackground()with nosignal.NotifyContext, so nothing local ever cancels it.Repro
Blocks for minutes instead of refusing with a structured error.
What already works
auth status(auth.go:48) is the well-behaved sibling: local-only, returns throughapp.OK, safe in any context.Suggested shape
NonInteractiveEnv, with a hint namingbasecamp auth status(to check) andBASECAMP_TOKEN(to authenticate headlessly) — same pattern as the setup gate.auth tokenalready has (app.Flags.JSON || app.Flags.Agent || app.Flags.JQFilter != "",auth.go:201) instead of writing prose to stdout.Nearest prior issues (#183, #194, #528) are all closed and none covers this.