Uh oh!
There was an error while loading. Please reload this page.
Fix #528: non-TTY installer hardcodes the Claude agent - #558
Conversation
The piped install (`curl … | bash`) takes the non-TTY branch of scripts/install.sh, which unconditionally ran `basecamp setup claude`. The post-login nudge likewise pointed at the first detected-unhealthy agent in registry order (Claude-first when both were present). Add `basecamp setup agents`: a non-interactive command that installs the baseline skill and connects coding agents by intent, not registry order. Selection is driven by BASECAMP_SETUP_AGENT (claude|codex|all|none); unset auto-detects — 0 detected installs the skill only, 1 connects it, ≥2 stays neutral and surfaces the per-agent commands. Every safety-critical outcome lives in a top-level flat field of the result envelope so it survives the styled renderer (which skips nested map/[]map); the `agents` array is JSON-only detail. `all` forces every registered handler and synthesizes symmetric missing-binary remediation (the Claude handler treats an absent binary as no-op success while Codex errors). - install.sh / install.ps1: both non-TTY and skip branches now run `setup agents`; install.sh gains an if-form source guard and documents BASECAMP_SETUP_AGENT. - auth.go: rewrite printAgentNudge — one detected-unhealthy agent prints its `setup <id>`; several print every choice directly, never guessing. - Regenerate .surface; add smoke declaration and e2e/installer.bats.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:76dc027cdb
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
This PR fixes the non-TTY installer and post-login “agent nudge” bias by introducing an intent-based, non-interactive basecamp setup agents command and routing non-interactive install paths through it, avoiding Claude-first guessing when multiple agents are present.
Changes:
- Added
basecamp setup agents(non-interactive) to install the baseline skill and connect agents based onBASECAMP_SETUP_AGENTor auto-detection. - Updated Bash/PowerShell installers to run
setup agentsin non-interactive / skip-wizard paths (instead of hardcodingsetup claude) and added a sourcing guard toinstall.sh. - Updated the login nudge logic to avoid Claude-first ordering and to surface explicit per-agent
basecamp setup <id>choices.
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.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/install.sh | Routes non-interactive install flows through setup agents; adds sourcing guard and documents BASECAMP_SETUP_AGENT. |
| scripts/install.ps1 | Runs setup agents for skip/non-interactive install flows and documents env selectors. |
| internal/commands/wizard.go | Registers the new setup agents subcommand under setup. |
| internal/commands/wizard_agents.go | Implements setup agents selection logic and the flat, styled-safe result envelope. |
| internal/commands/setup_agents_test.go | Adds JSON + styled-output regression coverage for setup agents behaviors and ordering contracts. |
| internal/commands/auth.go | Updates printAgentNudge to avoid “Claude-first” nudging and list explicit choices when needed. |
| internal/commands/auth_test.go | Adds test coverage for nudge behavior across none/single/multiple unhealthy detections. |
| install.md | Documents piped-installer behavior and how to force agent selection via BASECAMP_SETUP_AGENT. |
| e2e/smoke/smoke_lifecycle.bats | Marks setup agents as out-of-scope for smoke lifecycle. |
| e2e/installer.bats | Adds e2e assertions that installers dispatch to setup agents and that install.sh is safe to source. |
| .surface | Regenerates CLI surface snapshot to include basecamp setup agents. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
All reported issues were addressed across 11 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…kill docs Three follow-up fixes from review of #558: 1. Cross-version installer strategy (install.sh + install.ps1). The hosted install script from main can outrun the latest release, whose binary lacks `setup agents` (v0.7.2 has only `setup`/`setup claude`/`skill install`). Both scripts now probe the installed binary for `setup agents` support and, on an older binary, fall back WITHOUT reintroducing the Claude-first bug: only an explicitly selected agent is connected; an unset/auto/ambiguous selector installs the shared skill via `skill install`. Added old-binary regression tests in e2e/installer.bats. 2. Guard PowerShell post-install setup. Both calls now route through a single best-effort Invoke-PostInstallSetup helper wrapped in try/catch, so a nonzero native exit (under $ErrorActionPreference='Stop' + $PSNativeCommandUseErrorActionPreference) can no longer terminate an otherwise-successful install. 3. Skill docs. Document `setup agents` + BASECAMP_SETUP_AGENT in skills/basecamp/SKILL.md and add it to the remediation list in skills/basecamp-doctor/SKILL.md.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:267c7efebf
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
… binaries - setup agents: add cobra.NoArgs — selection is env-driven, so positional args (typos, or confusion with `setup <id>`) are now rejected, not silently ignored. - install.sh / install.ps1: on old release binaries lacking `setup agents`, BASECAMP_SETUP_AGENT=all now dispatches every per-agent `setup <id>` the binary supports instead of collapsing to skill-only. Explicit "all" is honored intent, distinct from the intent-neutral unset/auto/none fallback. PowerShell calls are now individually guarded so one agent's failure can't skip the rest. - Tests: NoArgs rejection (Go) + `all` old-binary dispatch (installer.bats).
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
On a pre-`setup agents` release (v0.7.2 advertises only `setup claude`), an explicit BASECAMP_SETUP_AGENT=codex ran `basecamp setup codex`. The old `setup` parent accepts `codex` as a stray positional arg and launches the INTERACTIVE setup/OAuth wizard — violating the non-interactive contract. Both installers now capability-check explicit claude|codex selectors the same way `all` already did: only dispatch `setup <id>` if the binary advertises it, otherwise degrade to `skill install` and never invoke the unknown subcommand. Tests: the old-binary stub now rejects every unadvertised `setup <sub>` (not just `setup agents`), and new coverage asserts explicit `codex` degrades to `skill install` (install.sh) plus a static guard on install.ps1's capability check.
Uh oh!
There was an error while loading. Please reload this page.
* Add post-deploy installer smoke canary The install endpoint serves main, so merging a change to scripts/install.sh deploys it immediately. #558 shipped a sourcing guard that broke the documented `curl | bash` path under `set -u`; the bats suite now gates that regression pre-merge (#568), but nothing checked the published installer post-merge. Add .github/workflows/installer-smoke.yml: nightly + workflow_dispatch + push-to-main (scripts/install.sh) runs of the canonical Quick Start command against the published endpoint, cache-busted with the commit SHA since raw.githubusercontent.com serves max-age=300. Two legs: Linux (modern bash) and macOS via stock /bin/bash, which asserts exactly Bash 3.2 through the same interpreter so the coverage can't silently degrade. Installs into a scratch HOME with explicit BASECAMP_BIN_DIR and asserts the deterministic installed path. Also two installer-scoped comment fixes from the incident review: correct the "Keep PATH empty" description in e2e/installer.bats (PATH is a binary-free tmpdir, not empty), and document why the guard's `:-$0` fallback is required so a future cleanup doesn't re-break piping. Refs #558, #568 * Set cancel-in-progress explicitly to match workflow convention
* ci: harden installer canary and add pre-merge Bash 3.2 gate Canary (installer-smoke.yml): - Trigger on scripts/install.ps1 pushes too — the raw URL serves main, so merge == deploy for the PowerShell installer as well. - New Windows job running the documented irm | iex shape under both Windows PowerShell 5.1 and pwsh 7 (matrix), against scratch USERPROFILE/BASECAMP_BIN_DIR under RUNNER_TEMP. - New notify job: on any leg's failure, file or update an "Installer canary failure" issue (exact-title dedupe, best-effort gh calls, ::error:: annotation). Skipped entirely on green runs. Pre-merge (test.yml): new installer-bash32 job on macos-latest — plain steps, not bats (bats 1.11 execs `env bash` at every layer, so the interpreter is not pinnable through it). Asserts /bin/bash is exactly 3.2, syntax-checks install.sh, and runs it piped with curl off PATH, asserting the guard's clean failure message and the absence of unbound-variable errors (the #558 regression class). Path-filtered to installer files plus test.yml itself so the job proves itself on the PR that introduces or edits it; skipped filters still report success, so the job is safe to mark required. Refs #558#568#569 * TEMP: notify drill (revert me) * Revert "TEMP: notify drill (revert me)" This reverts commit 37cf5f7. * ci: run a full offline install under Bash 3.2, not just the curl guard The piped-execution step stops at the curl prerequisite, and `bash -n` only checks grammar — neither catches Bash-4-only constructs in the download, checksum, extract, or post-install code. Add a step that runs one complete install path under real /bin/bash 3.2 using a fixture curl serving a local release (stub binary, real sha256 checksums) with PATH restricted to fixture + system dirs, then asserts the installed stub runs. Review feedback on #575. * ci: grant the 3.2 gate pull-requests read for paths-filter dorny/paths-filter enumerates changed PR files via the REST API and documents a pull-requests: read requirement; the job-level permissions block zeroes everything unlisted. Review feedback on #575.
Every CLI command runs root PersistentPreRunE -> appctx.NewApp -> auth.NewManager -> auth.NewStore, and credstore.NewStore eagerly probes OS keyring availability with a write. On macOS that probe shells out to an uncancellable `security` child; a locked keychain with no TTY or GUI blocks it forever — on *every* command, including credential-free ones like `setup agents` and `skill install`. That is how the installer hung headless (#568, canary discovery #569/#571 -> #574 drill lineage). Go fix (reaches users at the next release): Store now records the fallback dir at construction and builds the credstore lazily behind sync.Once on first credential operation. Credential-free commands never touch the keyring; credential-touching commands probe on first use exactly as before, with the documented BASECAMP_NO_KEYRING=1 escape hatch. No timeout was added deliberately: an abandoned goroutine cannot kill the hung `security` child, so a timeout would hide the hang while keeping the leak. Bounded fallback needs cancellation support upstream in credstore/go-keyring first. Installer belt (deploys on merge, covers released binaries <= v0.7.2 which still probe eagerly): post_install_setup in install.sh prefixes each best-effort child with per-command BASECAMP_NO_KEYRING=1; Invoke-PostInstallSetup in install.ps1 sets it for the duration of setup and restores the caller's value on exit. These children never touch credentials — the var only skips the old binaries' startup probe. Tests: keyring_test.go pins constructor laziness through an injectable credstore seam. installer.bats gains belt tests that explicitly unset the suite-wide BASECAMP_NO_KEYRING and assert both directions (real calls carry it, the sh capability probe does not), plus a pwsh test that extracts Invoke-PostInstallSetup from install.ps1's AST (no test hooks in the production script), runs it against a logging stub, and asserts set + restore; it fails closed when pwsh is missing in CI. The canary's workflow-level BASECAMP_NO_KEYRING is removed with no step-scoped replacement: the macOS leg now genuinely exercises the deployed belt (a missing belt = hung security child = timeout = notify), and the direct `basecamp version` asserts skip PersistentPreRunE entirely. Refs #558#568#569
* Defer keyring probe to first credential use; belt the installers Every CLI command runs root PersistentPreRunE -> appctx.NewApp -> auth.NewManager -> auth.NewStore, and credstore.NewStore eagerly probes OS keyring availability with a write. On macOS that probe shells out to an uncancellable `security` child; a locked keychain with no TTY or GUI blocks it forever — on *every* command, including credential-free ones like `setup agents` and `skill install`. That is how the installer hung headless (#568, canary discovery #569/#571 -> #574 drill lineage). Go fix (reaches users at the next release): Store now records the fallback dir at construction and builds the credstore lazily behind sync.Once on first credential operation. Credential-free commands never touch the keyring; credential-touching commands probe on first use exactly as before, with the documented BASECAMP_NO_KEYRING=1 escape hatch. No timeout was added deliberately: an abandoned goroutine cannot kill the hung `security` child, so a timeout would hide the hang while keeping the leak. Bounded fallback needs cancellation support upstream in credstore/go-keyring first. Installer belt (deploys on merge, covers released binaries <= v0.7.2 which still probe eagerly): post_install_setup in install.sh prefixes each best-effort child with per-command BASECAMP_NO_KEYRING=1; Invoke-PostInstallSetup in install.ps1 sets it for the duration of setup and restores the caller's value on exit. These children never touch credentials — the var only skips the old binaries' startup probe. Tests: keyring_test.go pins constructor laziness through an injectable credstore seam. installer.bats gains belt tests that explicitly unset the suite-wide BASECAMP_NO_KEYRING and assert both directions (real calls carry it, the sh capability probe does not), plus a pwsh test that extracts Invoke-PostInstallSetup from install.ps1's AST (no test hooks in the production script), runs it against a logging stub, and asserts set + restore; it fails closed when pwsh is missing in CI. The canary's workflow-level BASECAMP_NO_KEYRING is removed with no step-scoped replacement: the macOS leg now genuinely exercises the deployed belt (a missing belt = hung security child = timeout = notify), and the direct `basecamp version` asserts skip PersistentPreRunE entirely. Refs #558#568#569 * test(installer): pin restore of a caller-set BASECAMP_NO_KEYRING The pwsh belt test proved restore-to-unset only; a regression that overwrites an existing caller value would have passed. Second driver pass sets the var to 0 before Invoke-PostInstallSetup and asserts it survives. Review feedback on #578.
Fixes#528.
Both symptoms
setup claude— the piped install (curl -fsSL https://basecamp.com/install-cli | bash, a 302 redirect to this repo'sscripts/install.sh) took the non-TTY branch, which unconditionally ranbasecamp setup claude. TheBASECAMP_SKIP_SETUP=1branch did the same.printAgentNudgepointed at the first detected-unhealthy agent in registry order, so it always recommended Claude when both agents were present.Approach — detection proves presence, not intent
New
basecamp setup agentscommand (always non-interactive): installs the baseline skill and connects coding agents by intent.BASECAMP_SETUP_AGENTwins:claude|codex|all|none.setup <id>choices (never guess).allforces every registered handler and synthesizes symmetric missing-binary remediation — the Claude handler treats an absent binary as no-op success while Codex errors, so the aggregate runner fills the gap for either.BASECAMP_SKIP_SETUP=1skips only the interactive wizard;setup agentsstill runs.Styled-safe result envelope
curl | bashrenders styled output (piped stdin, TTY stdout), and the styled renderer skips nestedmap/[]map. So every safety-critical outcome —skill_installed,selector,ambiguous,detected_before,attempted_agents,errors(union of baseline + id-prefixed per-agent),warnings,manual_commands— lives in a top-level flat field; theagentsarray is JSON-only detail. Ordering is deterministic: set-like fields sorted by id;manual_commands/errorspreserve each handler's own sequence with stable first-seen dedup (Codex's remediation is an ordered marketplace-add → upgrade → plugin-add sequence, never lexicographically sorted).Changes
internal/commands/wizard_agents.go—newSetupAgentsCmd+runNonInteractiveAgentSetupand the typed envelope. Existingsetup claude/setup codexunchanged.internal/commands/auth.go— rewriteprintAgentNudge: one detected-unhealthy agent → itssetup <id>; several → every choice printed directly.scripts/install.sh/scripts/install.ps1— both non-TTY and skip branches runsetup agents; install.sh gains an if-form source guard and documentsBASECAMP_SETUP_AGENT..surfaceregenerated (onlysetup agentsadditions); smoke declaration + newe2e/installer.bats;install.mdupdated.Tests
internal/commands/setup_agents_test.go— full-CLI (cobra Execute) coverage: styled-output regression (failure survives via top-levelerrors), 0/1/≥2 detection, baseline-skill failure,=allzero/one/two,=codexmissing-binary contract, Codex manual-order preservation (guards against string-sorting),=claudeno-binary (symlink + detection flip + synthesized remediation),=none, invalid selector.internal/commands/auth_test.go— nudge: none / single / multiple detected-unhealthy.e2e/installer.bats— sourcing guard,post_install_setupdispatch (non-TTY + skip →setup agents, neversetup claude), static branch assertions for both scripts.Verified:
make build, targetedgo test,make check-surface/check-skill-drift/check-bare-groups/check-naming/check-smoke-coverage/fmt-check/vet/lint, and the smoke + installer bats suites all green. (The full unit suite has a pre-existing network-dependent todos-reposition test that hangs offline; unrelated to this change.)Summary by cubic
Replaces the non-TTY installer’s hardcoded Claude path with an intent-based, non-interactive agent setup. Adds
basecamp setup agents, makes installers cross-version safe, and prevents old releases from launching the interactive wizard.New Features
basecamp setup agents(non-interactive): installs the baseline skill and connects coding agents by intent; honorsBASECAMP_SETUP_AGENT=claude|codex|all|none(unset auto-detects: 0→skill only, 1→connect it, ≥2→skill only + surfacesetup <id>choices).scripts/install.sh,scripts/install.ps1) runsetup agentsfor non-TTY andBASECAMP_SKIP_SETUP=1. Docs updated with piped-install auto-connect and how to setBASECAMP_SETUP_AGENTin Bash/PowerShell.agentsarray is JSON-only detail.Bug Fixes
setup agents. On older binaries, fall back without reintroducing Claude-first behavior—allruns each supported per-agent setup; explicitclaude/codexis capability-checked and degrades toskill installif unsupported; all calls are guarded so failures never abort install.Invoke-PostInstallSetup: marks attempted agents on dispatch (not success) to match Bash, so=allonly falls back toskill installwhen no supported per-agent subcommands exist.setup agentsrejects positional args to prevent accidentalsetup <id>typos.basecamp setup <id>option (no bias).Written for commit 5cdc4ec. Summary will update on new commits.