feat: address Akshay's AuthKit friction log (agent/CI paths, port detection, messaging, install UX, env/auth safety, verify-login)#192
Conversation
detectPort now reads vite.config.{ts,js,mjs} for tanstack-start (modern
@tanstack/react-start is Vite-based), falling back to legacy Vinxi
app.config.ts, and the shared react/react-router/vanilla-js Vite loop is
extracted into a parseViteConfigPortFromDir helper.
Redirect-URI validators (Next.js, React Router, TanStack Start) now compare
the URI's effective port to the detected dev-server port for local hosts and
push an error-severity issue on mismatch, flipping validation to failed
instead of silently passing. Route-mismatch hints use the redirect URI's real
origin instead of a hardcoded localhost:3000.
Fixes the Akshay friction-log trust failure where a Lovable TanStack Start
app on port 8080 was configured against localhost:3000 yet certified as
"Validation passed".
Phase 4 (Install UX) of the Akshay friction-log fixes. Completion summary: - Add CompletionData type + pure buildCompletionData builder (lockfile-aware dev command via resolveDevCommand, app URL via detectPort, changed files, composed next steps, per-framework docs + dashboard URLs). - New buildingCompletion machine state + actor computes the payload between postInstall and complete; errors degrade to the static fallback box. - renderCompletionSummary renders the structured success box (files capped at 5, next steps, per-framework docs footer); falls back to the static box when no completion data is present. Failure branch unchanged. - Widen the complete event with an optional completion payload; all three adapters (CLI, Dashboard, Headless) render/emit it. Headless spreads the fields only when present, preserving the existing NDJSON shape. Agent progress: - CLI adapter: remove the 2s spinner-message reset that clobbered phase text; persist file:write/file:edit as append-only step lines above the spinner (stop -> log -> restart), path-only with consecutive-path dedupe. - Headless adapter: stream path-only file:write/file:edit NDJSON (never content). - Stretch: surface Bash commands via a new agent:tool event emitted from the post-permission tool_use branch; rendered as step lines (CLI) and NDJSON (headless). - Stretch: optional UIConfig.getSignInSnippet surfaced as a next step; defined for Next.js (client-side refreshAuth guidance, per the AuthKit skill). Validated: pnpm build, pnpm test (2241), pnpm typecheck, oxlint, oxfmt all pass.
Removes the two hard dead-ends an AI agent or CI pipeline hits first when driving the CLI non-interactively: - Bare `workos` in a non-TTY shell now emits the machine-readable command tree (JSON) or the fully-configured help, instead of a degenerate two-line block on stderr with empty stdout. Agents and CI can now discover `install`. - Non-interactive installs no longer hang on interactive prompts. `abortIfCancelled` fails fast with a structured `non_interactive_prompt` error (Layer 1), and Next.js, React Router, and upload-env apply graceful per-site defaults so installs succeed (Layer 2). - `--router app|pages` deterministically selects the Next.js router, winning over detection. WORKOS_MODE=ci now enforces the same required-arg validation as the hidden --ci flag and bridges to the downstream `options.ci` paths (git checks auto-continue, package-manager auto-select). Behavior change: `WORKOS_MODE=ci workos install` without --api-key/--client-id/--install-dir now errors with a structured missing_args message instead of falling through to auto-provisioning.
… semantics) Messaging-accuracy sweep with no behavior changes to install/auth flows. Area 1 — copy-pasteable command hints: route ~28 hardcoded `workos <subcommand>` hint sites through formatWorkOSCommand()/getWorkOSCommand() so users who launched via `npx workos@latest ...` are told to run the npx form instead of a bare `workos` that npx never installed. Adds a repo-walk regression guard (command-hints-guard.spec.ts) with a curated 4-entry allowlist for genuinely-static strings; it fails on any new unrouted hint. Area 2 — credential-source-aware copy: thread context.credentialSource into agentOptions and branch getOrAskForWorkOSCredentials so "Using the WorkOS credentials you provided" prints only for cli/manual (suppressed for device/stored/env, gated on human output). Adds a source field to staging:success so the CLI/headless adapters say the right thing instead of "retrieved automatically" for both fresh and reused environments. Area 3 — honest semantics + accurate migrations copy: env remove now states it is local-only (help + runtime warning + localOnly/wasUnclaimed JSON fields, warning that an unclaimed env's claim token is lost); env claim states claiming is permanent (help + runtime note + permanent JSON field); migrations description advertises the generic-CSV path (e.g. Supabase) via a shared MIGRATIONS_DESCRIPTION constant, and the JSON help subcommand list is reconciled with @workos/migrations v2.5.0 (adds export/export-template/ generate-package-template/validate-package; process-role-definitions → process-roles). Validation: build, typecheck, and full test suite (2277) pass; the hint guard is now a pnpm test gate. Reviewed via validation-only fallback (the ideation reviewer subagent was unavailable in this headless run).
Add `workos env provision`, a credentials-only subcommand that calls provisionUnclaimedEnvironment() directly (no auth, no code-gen). It emits credentials on stdout (JSON is the agent credential channel), stores the result as a local active unclaimed env so a follow-up `env claim` works, and never writes to the project directory or any .env file. Failures (incl. 429) surface as structured errors with no fallback to login. Rework auth login staging provisioning to stop silent cross-account switches. provisionStagingEnvironment now takes the authenticated account, detects a mismatch against the prior active env (ownerEmail wins over clientId), and on mismatch writes the new account's Staging under a distinct key instead of clobbering the active slot. runLogin prompts (human) / warns (agent-CI) / emits structured JSON, and always ends with an explicit "Now using" line. Stretch: persist ownerEmail/ownerUserId on environment records and carry them through markEnvironmentClaimed; add setActiveEnvironment(). Regression tests added first and confirmed failing on pre-fix code (env provision, mismatch/in-place-clobber guard, "Now using", owner fields).
Add `workos verify-login`, a top-level resource command that proves the AuthKit login loop end-to-end against the active environment with no browser and no email/OTP retrieval, closing the last agent-oriented gap in the friction log (an autonomous agent could not self-verify the post-login flow). The command runs a full round-trip against the bundled @workos-inc/node SDK: createUser (throwaway, emailVerified) -> authenticateWithPassword -> assert access + refresh tokens -> deleteUser in a finally block so cleanup always runs, even on auth failure. A failed cleanup is surfaced machine-readably (orphanedUserId + userCleanedUp:false) rather than swallowed, and does not change the exit code. Two unconditional safety rules (no override flag): production refusal on both env.type === 'production' and an sk_live_ key prefix, before any SDK call; and always-attempt cleanup. Output has human (checklist + verdict) and JSON (flat verdict object) modes driven by the global --json flag and non-TTY auto-detection. Auth-required exits 4 via resolveApiKey; refusal / missing client id emit the standard error envelope and exit 1; a failed verification emits the verdict with success:false and exits 1. The --method magic-auth stretch is not shipped: its gate (empirical staging confirmation that createMagicAuth returns the code in the API response) cannot be exercised in this headless run, so per the spec the flag is dropped and password grant remains the only shipped method. Resolves friction-log goal 10.
There was a problem hiding this comment.
This review was skipped because it would exceed your organization's monthly flex usage limit. Raise the limit in billing settings or wait until the next billing period resets limits.
| const config = getOrCreateConfig(); | ||
| config.environments['unclaimed'] = { | ||
| name: 'unclaimed', | ||
| type: 'unclaimed', | ||
| apiKey: result.apiKey, | ||
| clientId: result.clientId, | ||
| claimToken: result.claimToken, | ||
| }; | ||
| config.activeEnvironment = 'unclaimed'; | ||
| saveConfig(config); |
There was a problem hiding this comment.
🔍 Silent overwrite of existing unclaimed environment on repeated provision
The runEnvProvision function (src/commands/env.ts:153-162) unconditionally writes to the 'unclaimed' config key. If a user runs workos env provision twice without claiming the first environment, the first environment's claim token is silently replaced. The old claim token is permanently lost — there is no way to recover or claim that first environment. This is arguably by design (the test at src/commands/env.spec.ts:308-319 verifies the overwrite), but it could surprise agents that provision environments speculatively. A guard or warning before overwriting an existing unclaimed entry would make the behavior explicit.
Was this helpful? React with 👍 or 👎 to provide feedback.
Greptile SummaryThis PR improves the CLI’s non-interactive AuthKit install and verification flows. The main changes are:
Confidence Score: 4/5Mostly safe to merge after the CI dirty-tree path is fixed. One contained issue remains in the headless CI install flow; the other reviewed flows are straightforward and covered by focused tests.
What T-Rex did
Important Files Changed
|
Fixes every CLI-side item from Akshay Maniyar's AuthKit Hybrid + Full Agent friction log. Six implementation commits (plus a formatting pass), each independently gate-tested (
pnpm build && pnpm test && pnpm typecheckgreen at every commit; final suite 2,310 tests).Agent/CI path —
36c47e9npx workosin a non-TTY shell now emits the full JSON command tree on stdout (exit 0) instead of a degenerate two-option help dump (the old handler calledshowHelp()on a fresh yargs instance with no commands registered).abortIfCancelledfails fast with a structurednon_interactive_prompterror when prompts are disallowed — any future unguarded prompt dies loudly instead of hanging an agent forever.--router app|pagesflag (flag wins); without it, ambiguous Next.js router detection defaults to app router with a warning in non-interactive mode. Same graceful defaults for React Router's ambiguous branches and the upload-env prompt.WORKOS_MODE=cinow enforces the same behavior as the hidden--ciflag (dirty-tree auto-continue, package-manager auto-select) — previously two disconnected notions of "CI".Port detection + validation —
aa875e3detectPortparsesvite.config.{ts,js,mjs}first for TanStack Start (Lovable shape), with legacyapp.config.tsfallback. Previously the installer wrote port 3000 into.env.localand the dashboard (redirect/CORS/homepage) for an app running on 8080.localhost:3000.Messaging sweep —
bb2c2b1workos <subcommand>hints routed through the npx-awareformatWorkOSCommandhelper, enforced by a new repo-walk regression guard with a curated allowlist — new hardcoded hints failpnpm test.env removehelp states it is local-config-only;env claimstates claiming is permanent; migrations description reframed around the CSV path (e.g. Supabase) via a shared constant.Install UX —
f6c81d0buildCompletionDatabuilder assembles the lockfile-aware dev command, app URL with detected port, changed files, next steps, and per-framework docs/dashboard URLs, rendered by all three adapters (CLI/Dashboard/Headless) with a static fallback.setIntervalthat clobbered agent phase text;file:write/file:editevents stream as append-only step lines (CLI) and NDJSON (headless). Stretch:agent:toolBash surfacing + a Next.js sign-in-link snippet.Env provision + login safety —
f3452cbworkos env provision: credentials-only unclaimed environment (apiKey/clientId/claimTokenvia--json), no code-gen, no project writes — closes the agent workaround of runninginstallin throwaway directories just to extract keys.auth loginnever silently repoints the active environment to a different account: mismatches (ownerEmail/clientId comparison) prompt in human mode, warn in agent/JSON mode, and every login ends withNow using: <env> (<email>).verify-login —
738d8ccworkos verify-loginproves the AuthKit loop headlessly: throwaway user →authenticateWithPassword→ token assertion →deleteUserin afinallyblock. Unconditional production refusal (env type orsk_live_prefix) before any SDK call, no override flag. 13 spec cases; live smoke-tested against the real API.Merge note
Prefer rebase merge over squash: the commits are conventional-commit formatted (
fix:/feat:), so release-please picks up each as its own changelog entry. A squash would collapse them into one line.