Skip to content

Refuse browser and pasted-callback logins under BASECAMP_NONINTERACTIVE - #693

Merged
jeremy merged 4 commits into
mainfrom
login-noninteractive-gate
Sep 10, 2026
Merged

Refuse browser and pasted-callback logins under BASECAMP_NONINTERACTIVE#693
jeremy merged 4 commits into
mainfrom
login-noninteractive-gate

Conversation

@jeremy

@jeremy jeremy commented Sep 9, 2026

Copy link
Copy Markdown
Member

#681 gated interactive login on the explicit machine-output flags and left the environment half of #669 open: BASECAMP_NONINTERACTIVE=1 basecamp login still opened a browser (or, under SSH, printed a URL and read stdin for a pasted callback) and sat there for up to five minutes. The repo's own contract for the variable — "the CLI must not show interactive prompts regardless of TTY detection; they become actionable errors" (config.NonInteractiveEnv, SKILL.md) — is what this applies to login.

What

  • auth login / login refuse under BASECAMP_NONINTERACTIVE unless --device-code is given. The check sits after the machine-output gate and before any network call, and its hint names the two headless shapes: --device-code, which prints a code to approve from any device and asks nothing of the terminal (the exception Import personal access tokens with auth login --with-token #681 argued for), and --with-token, plus auth status for a check. Same usage-error shape as the flag gate.
  • The pasted-callback prompt refuses under the variable too (internal/auth). A device login on a Launchpad-backed host — every production account until the BC5 authorization server ships — falls back to remote mode, whose "Paste the callback URL" read is the one place a login consumes stdin. That prompt now returns the same actionable error before printing instructions, so the --device-code exemption cannot route a harness into a stdin wait.
  • SKILL.md's auth block gains the one-line rule; no surface change.

Verification

  • Failing first: on main, TestAuthLoginRefusesNonInteractiveEnvWithoutDeviceCode (default, --remote, --local, --no-browser) and TestLoginRemoteModeRefusesNonInteractiveEnv do not fail fast — they hang on the callback wait until the harness timeout, which is the reported behavior. With the gates they refuse with usage, name BASECAMP_NONINTERACTIVE, --device-code and --with-token, and the identity server sees no request; the remote test also asserts the paste prompt was never logged.
  • TestAuthLoginDeviceCodeRunsUnderNonInteractiveEnv proves the exemption end-to-end: a pinned-issuer device grant under the variable prints the code and completes.
  • e2e: auth.bats case for the env refusal (exit non-zero, hint names both flags).
  • bin/ci green on Linux (thelio, Go 1.26.7): fmt, vet, lint, unit, e2e, naming, surface, skill drift, bare groups, provenance, tidy.

Not doing here

  • Gating on a non-terminal stdin without the variable: the remote paste read already errors at EOF, and the browser flows never read stdin.
  • Bounding the device flow's poll with signal.NotifyContext: separate from this gate.

Fixes #669


Summary by cubic

Fixes #669: basecamp auth login and basecamp profile create now refuse browser and pasted-callback logins under BASECAMP_NONINTERACTIVE instead of opening a browser or waiting up to five minutes on a pasted callback. --device-code remains the exception, but only where the server offers the device flow — Launchpad does not.

Changes

  • The gate now runs at the command layer for both auth login and profile create, plus again in the auth layer for every converging Launchpad shape, so the refusal happens before any network call, stdin read, or in-memory profile entry.
  • LoginOptions.defaults treats the variable as --no-browser for every flow, so no login shape can launch a browser under it.
  • The usage error names --device-code and --with-token; SKILL.md documents the rule.

Written for commit 4c767d3. Summary will update on new commits.

Review in cubic

The machine-output flags already turn an interactive login into a usage
error; the environment variable that says the same thing did not, so a
harness that set it and ran the login sat on a browser callback for five
minutes, or on a pasted-URL prompt nobody would answer. The gate refuses
those flows up front, before discovery, and names the two headless shapes:
--device-code, which prints a code to approve from any device and asks
nothing of the terminal, and --with-token.

A device login on a Launchpad-backed host falls back to the pasted
callback, so that prompt refuses under the variable too, where it would
otherwise read stdin.

Fixes #669
Copilot AI balanced review requested due to automatic review settings September 9, 2026 23:30
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) skills Agent skills auth OAuth authentication labels Sep 9, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-09T23:45:13.154476Z 4c767d3 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Local Launchpad login remains interactive through other callers, and device-code guidance overstates current provider support.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds non-interactive safeguards intended to prevent OAuth login flows from waiting for browser or pasted-callback input.

Changes:

  • Gates interactive login under BASECAMP_NONINTERACTIVE.
  • Adds unit and end-to-end coverage.
  • Documents the device-code exception.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File summaries
File Description
internal/commands/auth.go Adds the command-level non-interactive gate.
internal/auth/auth.go Refuses remote callback prompting.
internal/commands/auth_login_test.go Tests refusal and device-code behavior.
internal/auth/auth_test.go Tests remote-mode refusal.
e2e/auth.bats Adds CLI-level refusal coverage.
skills/basecamp/SKILL.md Documents non-interactive authentication.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/auth/auth.go Outdated
Comment thread internal/commands/auth.go Outdated
Comment thread skills/basecamp/SKILL.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c80845f7f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/auth/auth.go Outdated
Comment thread skills/basecamp/SKILL.md Outdated
…the pasted callback

profile create reaches Manager.Login without the login command's gate, so
the local Launchpad branch still opened a browser and waited on the
loopback callback under the variable. Both Launchpad shapes wait on a
person, and every entry point converges on loginLaunchpad, so that is
where the refusal lives now. The hints and the skill stop promising
--device-code unconditionally: Launchpad has no device flow.
Copilot AI review requested due to automatic review settings September 9, 2026 23:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

BC5-backed profile creation can still open a browser under BASECAMP_NONINTERACTIVE without an explicit device-code request.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread internal/auth/auth.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5af0342e9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/auth/auth.go
…variable

profile create runs the same OAuth flows as login without the command
gate, so on a BC5 issuer it launched a browser to the approval page under
BASECAMP_NONINTERACTIVE. Both commands now share one refusal, and
LoginOptions.defaults treats the variable as --no-browser for every flow.
Copilot AI review requested due to automatic review settings September 9, 2026 23:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The profile gate returns after mutating in-memory configuration, bypassing rollback and failing the new regression test.

Review details

Suppressed comments (1)

internal/commands/profile.go:269

  • This returns after the pending profile has already been inserted and ActiveProfile/BaseURL changed, bypassing the rollback used for login errors below. A refused default profile create therefore leaves the in-memory config mutated (and makes the new assert.Empty(t, cfg.Profiles) case fail). Move this gate before the temporary mutation.
			app.Config.Profiles[name] = profileCfg
			app.Config.ActiveProfile = name
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI review requested due to automatic review settings September 9, 2026 23:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The gates cover all production login entry points and are backed by focused unit and end-to-end tests.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@jeremy
jeremy merged commit 43a36c0 into main Sep 10, 2026
26 checks passed
@jeremy
jeremy deleted the login-noninteractive-gate branch September 10, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth OAuth authentication commands CLI command implementations skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

basecamp login blocks and emits prose under machine output / non-interactive environments

2 participants