Skip to content

feat: address Akshay's AuthKit friction log (agent/CI paths, port detection, messaging, install UX, env/auth safety, verify-login)#192

Open
nicknisi wants to merge 7 commits into
mainfrom
nicknisi/akshay
Open

feat: address Akshay's AuthKit friction log (agent/CI paths, port detection, messaging, install UX, env/auth safety, verify-login)#192
nicknisi wants to merge 7 commits into
mainfrom
nicknisi/akshay

Conversation

@nicknisi

@nicknisi nicknisi commented Jul 8, 2026

Copy link
Copy Markdown
Member

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 typecheck green at every commit; final suite 2,310 tests).

Agent/CI path — 36c47e9

  • Bare npx workos in 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 called showHelp() on a fresh yargs instance with no commands registered).
  • abortIfCancelled fails fast with a structured non_interactive_prompt error when prompts are disallowed — any future unguarded prompt dies loudly instead of hanging an agent forever.
  • New --router app|pages flag (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=ci now enforces the same behavior as the hidden --ci flag (dirty-tree auto-continue, package-manager auto-select) — previously two disconnected notions of "CI".

Port detection + validation — aa875e3

  • detectPort parses vite.config.{ts,js,mjs} first for TanStack Start (Lovable shape), with legacy app.config.ts fallback. Previously the installer wrote port 3000 into .env.local and the dashboard (redirect/CORS/homepage) for an app running on 8080.
  • All three JS redirect validators now flag a redirect-URI/dev-port mismatch on local hosts as a validation error — no more "Validation passed" over a broken install. Remediation hints use the real redirect origin instead of hardcoded localhost:3000.
  • The 11 new regression tests were stash-verified to fail on pre-fix code.

Messaging sweep — bb2c2b1

  • ~28 live hardcoded workos <subcommand> hints routed through the npx-aware formatWorkOSCommand helper, enforced by a new repo-walk regression guard with a curated allowlist — new hardcoded hints fail pnpm test.
  • "Using provided WorkOS credentials" only renders when the user actually provided credentials (source threaded to both adapters).
  • env remove help states it is local-config-only; env claim states claiming is permanent; migrations description reframed around the CSV path (e.g. Supabase) via a shared constant.

Install UX — f6c81d0

  • Post-install summary no longer discards the rich summary on success: a pure buildCompletionData builder 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.
  • Removed the 2s setInterval that clobbered agent phase text; file:write/file:edit events stream as append-only step lines (CLI) and NDJSON (headless). Stretch: agent:tool Bash surfacing + a Next.js sign-in-link snippet.

Env provision + login safety — f3452cb

  • workos env provision: credentials-only unclaimed environment (apiKey/clientId/claimToken via --json), no code-gen, no project writes — closes the agent workaround of running install in throwaway directories just to extract keys.
  • auth login never 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 with Now using: <env> (<email>).

verify-login — 738d8cc

  • workos verify-login proves the AuthKit loop headlessly: throwaway user → authenticateWithPassword → token assertion → deleteUser in a finally block. Unconditional production refusal (env type or sk_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.

nicknisi added 6 commits July 7, 2026 15:55
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.

@greptile-apps greptile-apps 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.

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.

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread src/commands/env.ts
Comment on lines +153 to +162
const config = getOrCreateConfig();
config.environments['unclaimed'] = {
name: 'unclaimed',
type: 'unclaimed',
apiKey: result.apiKey,
clientId: result.clientId,
claimToken: result.claimToken,
};
config.activeEnvironment = 'unclaimed';
saveConfig(config);

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.

🔍 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR improves the CLI’s non-interactive AuthKit install and verification flows. The main changes are:

  • Emits machine-readable help for bare non-TTY CLI runs.
  • Adds deterministic router/mode defaults and command-hint messaging.
  • Adds unclaimed environment provisioning and safer login environment switching.
  • Adds redirect port validation and richer install completion output.
  • Adds workos verify-login for headless AuthKit login checks.

Confidence Score: 4/5

Mostly 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.

src/lib/adapters/headless-adapter.ts, src/lib/run-with-core.ts

T-Rex T-Rex Logs

What T-Rex did

  • Validated the CI dirty-tree handling by running a focused harness against the HeadlessAdapter in CI mode, reproducing the dirty-tree scenario and showing two behaviors based on the presence of --no-git-check: without the flag it reported git:status dirty and attempted to exit, while with the flag it continued and did not exit.
  • Collected and inspected the artifact logs from the build, test, typecheck, and summary steps; all commands exited with code 0 and the summary notes that no fallback commands were required.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/lib/adapters/headless-adapter.ts Adds headless progress and completion behavior, but dirty git handling still ignores CI mode unless --no-git-check is set.
src/lib/run-with-core.ts Threads completion-data building and headless options through installer core, but does not pass CI state to the headless adapter.
src/commands/verify-login.ts Introduces headless login verification with production refusal and cleanup in a finally block.
src/commands/env.ts Adds credential-only unclaimed env provisioning and clarifies local-only env removal behavior.
src/commands/login.ts Adds account ownership tracking to prevent silent active-environment switching on cross-account login.
src/lib/port-detection.ts Extends TanStack Start port detection to prefer Vite config before legacy app config fallback.
src/lib/validation/validator.ts Adds local redirect URI port mismatch validation for Next.js, React Router, and TanStack Start.
src/bin.ts Registers verify-login, env provision, router options, and non-interactive command-tree output.
src/integrations/nextjs/utils.ts Adds deterministic --router handling and non-interactive fallback for ambiguous router detection.
src/integrations/react-router/utils.ts Adds non-interactive defaults for ambiguous React Router mode detection.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User as User/Agent
participant Bin as bin.ts
participant Core as runWithCore/installerMachine
participant Adapter as CLI/Headless Adapter
participant WorkOS as WorkOS APIs
participant Project as Project Files

User->>Bin: workos install / env provision / verify-login
Bin->>Core: build options (router, ci, credentials)
Core->>Adapter: stream installer events
Core->>WorkOS: provision/configure/verify environment
Core->>Project: write env + integration files
Core->>Core: validate redirect port and build completion data
Core->>Adapter: complete with dev command, URL, files, next steps
Adapter-->>User: human summary or NDJSON/JSON output
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User as User/Agent
participant Bin as bin.ts
participant Core as runWithCore/installerMachine
participant Adapter as CLI/Headless Adapter
participant WorkOS as WorkOS APIs
participant Project as Project Files

User->>Bin: workos install / env provision / verify-login
Bin->>Core: build options (router, ci, credentials)
Core->>Adapter: stream installer events
Core->>WorkOS: provision/configure/verify environment
Core->>Project: write env + integration files
Core->>Core: validate redirect port and build completion data
Core->>Adapter: complete with dev command, URL, files, next steps
Adapter-->>User: human summary or NDJSON/JSON output
Loading

Comments Outside Diff (1)

  1. src/lib/adapters/headless-adapter.ts, line 190-205 (link)

    P1 CI dirty-tree handling
    WORKOS_MODE=ci still fails on dirty working trees here because the headless adapter only continues when --no-git-check is set. buildOptions() maps CI mode into options.ci, but runWithCore() never passes that into HeadlessAdapter, so CI installs with uncommitted files emit git_dirty and exit instead of auto-continuing as intended.

    Artifacts

    Repro: HeadlessAdapter CI dirty-tree harness

    • Contains supporting evidence from the run (text/javascript; charset=utf-8).

    Repro: harness output showing CI cancellation without noGitCheck and continuation with noGitCheck

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/lib/adapters/headless-adapter.ts
    Line: 190-205
    
    Comment:
    **CI dirty-tree handling**
    `WORKOS_MODE=ci` still fails on dirty working trees here because the headless adapter only continues when `--no-git-check` is set. `buildOptions()` maps CI mode into `options.ci`, but `runWithCore()` never passes that into `HeadlessAdapter`, so CI installs with uncommitted files emit `git_dirty` and exit instead of auto-continuing as intended.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/lib/adapters/headless-adapter.ts:190-205
**CI dirty-tree handling**
`WORKOS_MODE=ci` still fails on dirty working trees here because the headless adapter only continues when `--no-git-check` is set. `buildOptions()` maps CI mode into `options.ci`, but `runWithCore()` never passes that into `HeadlessAdapter`, so CI installs with uncommitted files emit `git_dirty` and exit instead of auto-continuing as intended.

Reviews (1): Last reviewed commit: "chore: formatting" | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant