feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: auto-claim keyless applications on login - #157

Merged
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim
Apr 21, 2026
Merged

feat: auto-claim keyless applications on login#157
rafa-thayto merged 11 commits into
mainfrom
rafa-thayto/auto-claim

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add keyless application lifecycle (keyless.ts): create accountless apps via BAPI, write framework-specific keys to .env.local, manage .clerk/keyless.json claim token breadcrumb
  • Add autoclaim orchestrator (autoclaim.ts): after clerk auth login, detect keyless breadcrumb and automatically claim the application via PLAPI — never throws, returns discriminated union so login is never interrupted
  • Auto-pull environment variables after successful claim — no manual clerk env pull step needed
  • Extract shared linkApp() from autolink.ts for reuse by both autolink (key detection) and autoclaim (keyless claim)
  • Add claimApplication() PLAPI endpoint (POST /v1/platform/accountless_applications/claim)
  • Integrate keyless setup into clerk init bootstrap when user skips auth
  • Integrate autoclaim into clerk auth login with contextual warnings and next-steps per failure mode
  • Truncate app name to 50 chars to match backend validation limit

How it works

  1. clerk init (skip auth) → creates accountless app, writes keys to .env.local, stores claim token in .clerk/keyless.json
  2. Developer builds their app using temporary dev keys
  3. clerk auth login → after OAuth, reads breadcrumb, calls claim endpoint, links app to account, auto-pulls env vars, clears breadcrumb
  4. Failure handling: transient errors (5xx) preserve breadcrumb for retry on next login; terminal errors (404/403) clear it; env pull failures warn but don't block login

Cleanup pass (commit 6cda4f5)

Applied KISS/DRY simplification across the new surface and touched neighboring code where it shared the same patterns. Net: -121 lines (107 insertions, 228 deletions).

  • plapi.ts: extracted shared plapiRequest<T> helper; 6 near-identical fetch-with-auth blocks (20 lines each) collapsed to 2–3 line call sites. Helper normalizes URL construction, query-param serialization, auth headers, PlapiError throw, and JSON parsing.
  • errors.ts: promoted errorMessage(unknown): string out of commands/doctor/checks.ts so autoclaim.ts and init/index.ts can reuse it instead of re-implementing error instanceof Error ? error.message : String(error) inline.
  • keyless.ts: replaced two TOCTOU Bun.file(...).exists() + read patterns with atomic .text().catch(() => "") / .json().catch(...); parallelized independent detectPublishableKeyName + detectSecretKeyName with Promise.all; factored .env.local / .clerk / keyless.json into named constants.
  • autoclaim.ts: table-driven classifyClaimError — replaced back-to-back error instanceof PlapiError && error.status === N branches with a TERMINAL_BY_STATUS: Record<number, Terminal["status"]> lookup.
  • Comments: removed file-level docstring blocks, section-header banners, and WHAT/narration comments from new files per house style (kept non-obvious WHY comments).
  • Tests: as neveras Profile, dropped the spies[] array in favor of explicit per-spy mockRestore(), deleted redundant setup commentary.

Test plan

  • 9 unit tests for autoclaim (all status paths, breadcrumb preservation, env pull success/failure)
  • 15 unit tests for keyless (BAPI calls, env writing, breadcrumb I/O, token parsing)
  • Login tests pass with autoclaim mocked
  • Full test suite passes (73 suites)
  • Lint, format, and typecheck clean
  • CI green after cleanup pass
  • Requires backend PLAPI claim endpoint to be deployed first (CLI gracefully no-ops if endpoint is unavailable)
  • E2E: clerk init -y → skip auth → verify .env.local and .clerk/keyless.json created → clerk auth login → verify app claimed, linked, and env vars refreshed

@rafa-thayto
rafa-thayto marked this pull request as ready for review April 17, 2026 17:11
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a56b83d to d338623CompareApril 17, 2026 17:11
@changeset-bot

changeset-botBot commented Apr 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d90fd0a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
clerkMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49d72fb4-1407-475b-8a10-ef138f3c541a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93fe4 and d90fd0a.

📒 Files selected for processing (19)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/lib/keyless.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/test/lib/stubs.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts

📝 Walkthrough

Walkthrough

Adds end-to-end keyless autoclaim: new lib/autoclaim.ts (exports attemptAutoclaim, AutoclaimResult) and lib/keyless.ts (create accountless app, write keys to env, parse claim token, manage .clerk/keyless.json). plapi.ts gains claimApplication. autolink.ts gains linkApp and delegates linking. auth/login invokes autoclaim and routes NEXT_STEPS by claim result. NEXT_STEPS extended. Shared errorMessage helper extracted and reused. Numerous Bun tests and docs updated; init now performs keyless setup and writes temporary keys.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 26.32% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat: auto-claim keyless applications on login' directly and concisely describes the main feature addition—automatic claiming of keyless applications upon user login.
Description check✅ PassedThe description is comprehensive and directly related to the changeset, covering the keyless lifecycle, autoclaim orchestration, environment variable handling, and integration points with clear implementation details.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch 2 times, most recently from 76b9642 to 55ea73eCompareApril 17, 2026 22:17

@wyattjohwyattjoh 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.

Code Review — PR #157

Reviewed with Opus + Codex second-opinion validation.

Blocker

B1. .clerk/keyless.json stores a live claim token with no .gitignore protection(codex confirmed)
packages/cli-core/src/lib/keyless.ts:88-106 writes claimToken under .clerk/ in the user's project. Nothing in this PR (or clerk init today) appends .clerk/ to the project's .gitignore. A developer can git add . and commit the token to a public repo, at which point any third party can claim the app before the original developer logs in, hijacking an app that will later hold production workloads.

Suggestion: in setupKeylessApp or writeKeylessBreadcrumb, append .clerk/ to .gitignore if not already present (mirroring what create-next-app and similar tools do). Add a test that verifies the .gitignore update.

B2. Keyless init writes .env.local but post-claim pull writes the framework default, producing two env files with drifting keys
This is a cross-file interaction with existing code rather than a change in this PR alone, but the PR is where the regression becomes user-visible:

  • packages/cli-core/src/lib/keyless.ts:57-76 hardcodes const targetFile = join(cwd, ".env.local").
  • packages/cli-core/src/lib/framework.ts (unchanged) declares envFile: ".env" for Next.js, Astro, and Nuxt.
  • packages/cli-core/src/commands/env/pull.ts:30-46 (resolveTargetFile) prefers the framework-detected file.

Net effect on a Next.js project: .env.local written at init, .env written by pull after claim, same keys, different values.

Suggestion: use detectEnvFile(cwd) in writeKeysToEnvFile so there is one canonical target. Also update the JSDoc that references .env.local.

B3. Missing changeset
git diff main..HEAD -- .changeset/ is empty. This is a feat: PR touching @clerk/cli-core runtime. The Enforce Changeset workflow will block merge.

Major

M1. App name sent to backend uses basename(cwd).slice(0, 50) with no sanitization(codex confirmed)
packages/cli-core/src/lib/autoclaim.ts:32:

  • Leaks potentially-sensitive directory names to the Clerk platform API.
  • .slice(50) truncates on UTF-16 code units; an emoji or non-BMP char at the boundary produces an orphaned surrogate (400 on strict UTF-8 validators).
  • Backend-invalid chars are not sanitized.

Suggestion: derive the name from package.json#name or the git repo name with basename(cwd) as a fallback, and sanitize + byte-length-truncate before sending.

M2. attemptAutoclaim(cwd) threads cwd but tryPullEnv ignores it
autoclaim.ts:37-50 accepts a cwd argument for the breadcrumb but calls pull({}), which uses process.cwd() internally (pull.ts:53). Today's single call site passes process.cwd() so it is benign, but this is fragile. Either thread cwd through pull options or assert at entry.

M3. createAccountlessApp has no timeout or abort; clerk init hangs on captive portal / slow BAPI(codex confirmed)
packages/cli-core/src/lib/keyless.ts:35-52 and packages/cli-core/src/commands/init/index.ts:272-287: fetch(...) has no AbortController. On slow/broken networks, withSpinner hangs indefinitely. log.debug is only visible with --verbose, so users see nothing.

M4. setupKeylessApp swallows all errors while success copy still renders(codex confirmed)
commands/init/index.ts:270-287 wraps the body in try { ... } catch { log.debug(...) }. Even on filesystem errors, parseClaimToken throws, or the fetch fails, printKeylessInfo still tells the user "Your app is ready with development keys in .env.local". Distinguish expected network failures from unexpected bugs and suppress the success message on failure.

M5. classifyClaimError semantic mismatch and aggressive breadcrumb-clear
autoclaim.ts:62-74 maps 403 to already_claimed, but the message at login.ts:128-132 says "your account does not have an active organization". Name and message disagree; rename the status (e.g. no_organization). Also, 404 unconditionally clears the breadcrumb; a transient DNS blip that returns 404 would destroy the retry signal.

Minor

  • readKeylessBreadcrumb (keyless.ts:108-119) has no schema validation; a malformed-but-JSON file yields {claimToken: undefined} and autoclaim.ts:33 will POST {token: undefined}.
  • parseClaimToken uses https://placeholder.com as the synthetic URL base (keyless.ts:81-87); prefer https://example.invalid (RFC 6761).
  • Em-dashes added in login.ts:130-131,163 and autoclaim.ts:7 (global style rule).
  • handleAutoclaim prints log.success then log.warn then outro (login.ts:117-124,135-148); noisy ordering.
  • No retry for 5xx / 429 despite the design claim that "transient failures preserve breadcrumb for retry on next login" (autoclaim.ts:28-36); a short in-process retry with backoff would save users a manual re-login.

Nits

  • CLAIM_WARNINGS could be an explicit object-literal map of the three warning statuses.
  • log.info("Environment variables written to .env.local") will be wrong once B2 is fixed.
  • Test expect(parsed.name).toBeTruthy() (autoclaim.test.ts:164) is too loose; assert the exact truncation.
  • CI refactor (composite action, workflow_call) is bundled into a feat: PR; splitting would simplify bisect.

Positives

AutoclaimResult discriminated union is cleanly typed and enables exhaustive handling in loginNextSteps. attemptAutoclaim never throws (the type system enforces it). linkApp extraction in autolink.ts is a good refactor. Test coverage for the orchestrator is thorough: all status paths, breadcrumb clear/preserve semantics, env-pull success/failure, linkApp invocation, and request body shape.

rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login

@wyattjohwyattjoh 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.

Code review

Substantial new surface (keyless.ts, autoclaim.ts) plus a clean plapi.ts refactor. Core design — discriminated AutoclaimResult, never-throws contract, breadcrumb-preserve-on-transient — is sound. A handful of issues worth tightening before merge: one user-facing inaccuracy (.env.local message), one misleading API parameter (pull({ cwd })), and a few error-handling edges (retry loops, corrupted breadcrumb recovery, silent keyless creation failures).

Comment threadpackages/cli-core/src/commands/init/heuristics.ts Outdated
Comment threadpackages/cli-core/src/commands/env/pull.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
Comment threadpackages/cli-core/src/lib/errors.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/plapi.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from a688ed0 to 1512ec2CompareApril 20, 2026 21:55
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 1512ec2 to 26be03fCompareApril 20, 2026 21:59

@wyattjohwyattjoh 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.

Solid feature overall. The main concerns are an existing-session path that bypasses auto-claim entirely, a raw fetch() call that skips the project's loggedFetch wrapper, a success-log UX wart where both a success line and a Run env pull manually warn can print together, and an overly broad 4xx-is-terminal classifier. See inline comments.

Comment threadpackages/cli-core/src/commands/auth/login.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/init/index.ts Outdated
Comment threadpackages/cli-core/src/lib/autoclaim.ts
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
B1: add .clerk/ to .gitignore in writeKeylessBreadcrumb to prevent
claim token from being committed to public repos
B2: use detectEnvFile(cwd) in writeKeysToEnvFile so init and claim
write to the same env file (e.g. .env for Next.js, not .env.local)
B3: add changeset (minor) for the autoclaim feature
M1: derive app name from package.json#name with basename fallback;
byte-length-truncate to 50 to avoid surrogate issues
M2: thread cwd through tryPullEnv -> pull() instead of relying on
process.cwd() internally
M3: add 15s AbortController timeout to createAccountlessApp to
prevent hanging on captive portal / slow BAPI
M4: move printKeylessInfo() inside try block so success copy is
suppressed on failure
M5: rename already_claimed -> no_organization to match the actual
403 semantic (no active organization); fix em-dashes in messages
nit: use https://example.invalid instead of https://placeholder.com
nit: add schema validation to readKeylessBreadcrumb
nit: tighten autoclaim test name assertion (exact value, not truthy)
nit: add gitignore idempotency test + wrong-shape breadcrumb test
docs(changeset): Automatically claim and link keyless applications on clerk auth login
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 26be03f to da939c9CompareApril 20, 2026 23:58
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
HIGH:
- printKeylessInfo now accepts envFile param so Next.js/Astro/Nuxt
users see the correct file name (.env) instead of hardcoded .env.local
- drop cwd option from EnvPullOptions — resolveAppContext internally
uses process.cwd() so a partial cwd thread is misleading; only
caller passes process.cwd() already
MEDIUM:
- broaden terminal 4xx: all 4xx except 408/429 clear breadcrumb to
prevent infinite retry loops on 400/409/422 responses
- readKeylessBreadcrumb now clears corrupted/wrong-shape breadcrumb
files so the user can recover via re-running clerk init
- setupKeylessApp escalates creation failure from log.debug to
log.warn with recovery hint (clerk auth login + clerk link)
- createAccountlessApp throws BapiError instead of plain Error to
match project error-handling protocol
LOW:
- remove unused AUTOCLAIM_FAILED error code
- switch to char-based truncation (Array.from) for app name —
simpler than byte-slice and matches likely backend char semantics
- add RFC 6761 comment on example.invalid sentinel in parseClaimToken
Tests: add 400/429 terminal/retryable tests; verify wrong-shape
breadcrumb is cleared from disk; update BapiError assertion
rafa-thayto added a commit that referenced this pull request Apr 20, 2026
- Run autoclaim on agent-mode existing-session path (not just post-OAuth)
- Use loggedFetch in createAccountlessApp (bapi tag; verbose shows URL+body)
- Drop duplicate log.warn for failed env pull (next-steps already covers it)
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Use Intl.Segmenter for grapheme-safe name truncation
- Update init/README.md and auth/README.md with keyless API endpoints and
breadcrumb lifecycle docs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/keyless.ts`:
- Around line 117-126: The writeKeylessBreadcrumb function currently writes the
breadcrumb file before ensuring the .gitignore entry, risking an unignored
secret if the process fails; change the order or add cleanup: call
ensureGitignoreEntry(cwd, BREADCRUMB_DIR + "/") before writing the file, then
create the directory and write the breadcrumb to breadcrumbPath(cwd);
alternatively, if you prefer to keep the current order, wrap the
Bun.write(breadcrumbPath(cwd), ...) call in try/catch and on any failure remove
the partial file (using breadcrumbPath(cwd)) so no unignored token remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f39bbd3-a4c1-4dae-b020-21d3ad603374

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea73e and da939c9.

📒 Files selected for processing (18)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/README.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/auth/login.ts
  • packages/cli-core/src/commands/doctor/checks.ts
  • packages/cli-core/src/commands/doctor/index.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/lib/autolink.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/keyless.test.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/test/lib/stubs.ts
✅ Files skipped from review due to trivial changes (4)
  • .changeset/auto-claim.md
  • packages/cli-core/src/commands/auth/login.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/auth/login.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/next-steps.ts
  • packages/cli-core/src/commands/init/heuristics.ts
  • packages/cli-core/src/lib/autoclaim.test.ts
  • packages/cli-core/src/lib/autoclaim.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/keyless.test.ts

Comment threadpackages/cli-core/src/lib/keyless.ts Outdated
rafa-thayto added a commit that referenced this pull request Apr 21, 2026
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from da939c9 to 2c93fe4CompareApril 21, 2026 14:25
Add machine-readable error code for autoclaim failures, available
for agent mode and CI error classification.
Add PLAPI client function for POST /v1/platform/accountless_applications/claim.
Sends claim token and app name, returns the claimed Application.
Extract profile-building logic from autolink() into a reusable linkApp()
function. This allows both autolink (key detection) and autoclaim
(keyless claim) to share the same profile persistence code.
Add keyless.ts for managing accountless Clerk applications:
- createAccountlessApp(): calls BAPI to create app with no auth
- writeKeysToEnvFile(): writes framework-specific keys to .env.local
- parseClaimToken(): extracts token from claim URL
- Breadcrumb I/O: read/write/clear .clerk/keyless.json
Includes 15 unit tests covering all functions and edge cases.
Add autoclaim.ts that detects keyless projects and claims them
automatically after clerk auth login. Never throws — returns a
discriminated union (claimed|not_found|already_claimed|failed|not_keyless)
so the login flow is never interrupted.
Key behaviors:
- Truncates app name to 50 chars (backend limit)
- Transient errors (5xx) preserve breadcrumb for retry
- Terminal errors (404/403) clear breadcrumb
Includes 8 unit tests covering all status paths.
When bootstrap mode skips authentication, create an accountless app
via BAPI, write keys to .env.local, and store the claim token in
.clerk/keyless.json for autoclaim on next login.
Also simplifies the keyless info message to promote the autoclaim
flow instead of requiring manual clerk link + env pull.
Wire autoclaim into the login flow with improved UX:
- Specific warning messages per failure cause (expired token,
missing org, transient error) instead of generic fallback
- Contextual next-steps based on claim result: manual link for
terminal failures, retry guidance for transient errors
- Mock autoclaim in login tests to isolate from transitive deps
After a successful autoclaim, automatically run `env pull` to refresh
.env.local with the claimed app's keys — no manual step needed.
- Extract shared plapiRequest<T> helper; collapse 6 PLAPI functions to
2-3 lines each (6 near-duplicate fetch blocks → one)
- Promote errorMessage() from doctor/checks.ts to lib/errors.ts so it
can be shared with autoclaim.ts and init/index.ts
- Replace two TOCTOU file.exists()+read patterns in keyless.ts with
atomic .text()/.json() catch-ENOENT variants
- Parallelize detectPublishableKeyName + detectSecretKeyName in
writeKeysToEnvFile (independent I/O)
- Table-drive classifyClaimError (404/403 status → result mapping)
- Drop file-level docstrings, section-header banners, and WHAT/
narration comments across new files per house style
- Use loggedFetch in createAccountlessApp (bapi tag, --verbose support)
- Write .gitignore entry before breadcrumb file to prevent exposure on
interrupted writes
- Run autoclaim on agent-mode existing-session path, not just post-OAuth
- Narrow 4xx terminal classification to explicit 404/403 only; all other
errors preserve the breadcrumb for retry on next login
- Drop duplicate log.warn for failed env pull (next-steps covers it)
- Use Intl.Segmenter for grapheme-safe app name truncation
- Remove unused AUTOCLAIM_FAILED error code
- Pass envFile from ctx to printKeylessInfo and setupKeylessApp so the
correct env file is shown per framework (Next.js/.env vs .env.local)
- Corrupt keyless.json now cleared on read so fresh setup can proceed
- Add isAuthenticated() credential-presence check for init keyless flow
- Update init/README.md and auth/README.md with keyless API endpoints
and breadcrumb lifecycle documentation
@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/auto-claim branch from 2c93fe4 to bf66e60CompareApril 21, 2026 14:36
Comment threadpackages/cli-core/src/commands/init/index.ts
Comment threadpackages/cli-core/src/lib/autoclaim.ts Outdated
Comment threadpackages/cli-core/src/commands/auth/login.ts Outdated
Comment threadpackages/cli-core/src/lib/keyless.ts
Comment threadpackages/cli-core/src/lib/keyless.ts
- autoclaim: wrap linkApp in tryLinkApp helper so a local config
write failure after a successful server-side claim cannot surface
as a failed login (preserves the never-throws contract)
- login: clarify that failed-autoclaim retry happens on next
`clerk auth login`, not just any subsequent command
- keyless: raise breadcrumb shape-mismatch auto-repair log from
debug to warn so users running without --verbose see the fix
- init: differentiate AbortError (15s timeout) with a specific
"Could not reach api.clerk.com within 15s" message
- init tests: stub createAccountlessApp / writeKeysToEnvFile /
writeKeylessBreadcrumb so keyless init tests stop minting real
accountless apps against production BAPI on every test run
@rafa-thayto
rafa-thayto merged commit 9abc17e into mainApr 21, 2026
10 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/auto-claim branch April 21, 2026 20:08
@github-actionsgithub-actionsBot mentioned this pull request Apr 21, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@wyattjoh