Skip to content

feat: guest login credentials, stale-key handling, and sign-in link refresh#306

Draft
leggetter wants to merge 11 commits into
mainfrom
feat/cli-guest-tracking-fixes
Draft

feat: guest login credentials, stale-key handling, and sign-in link refresh#306
leggetter wants to merge 11 commits into
mainfrom
feat/cli-guest-tracking-fixes

Conversation

@leggetter

@leggetter leggetter commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Companion PR

Coordinate with hookdeck/core #5233 — that PR owns API, Dashboard CliAuth, Console analytics, and L3 journeys. Merge core first, then this PR.


Why we're doing this

Same motivations as core #5233 — primarily guest sandbox claim (Journey A) and better tracking. Most CLI behaviour already existed; this PR wires guest attestation on POST /cli-auth, hardens stale-key handling, refreshes expiring listen links, and adds acceptance tests so claim-path changes do not break the alternate login paths.

  1. Product / UX — default hookdeck login after listen claims and upgrades the same guest sandbox (signupGuest), so the developer keeps their Console work. The CLI sends guest attestation on POST /cli-auth (guest_user_id + guest_api_key) so the server can prove the caller is that guest.
  2. Product — long listen sessions must not show an expired Console sign-in link; refresh the 1h /signin/guest?token=… URL automatically.

Measurement note: only Journey A gets a clean, linked conversion. Journeys B and C are existing flows — we regression-test them (acceptance + core L3) and rely on core #5233 for new instrumentation (origin_guest_user_id, abandon event, PostHog identity sync).


CLI behaviour (no flags — follows CLI profile)

Behaviour is driven by what's stored in the profile after listen / logout. There is no auth_intent flag and no TTY prompt.

Scope of this PR: changes target Journey A and supporting infrastructure (guest attestation, stale keys, link refresh). Journeys B and C are existing paths — covered by acceptance/L3 tests, not new user-facing behaviour.

Path When POST /cli-auth body Browser opens Outcome
A — Claim sandbox (default) Guest profile present (guest_url set) guest_user_id + guest_api_key (guest attestation) Signup → CliAuth Same user upgraded; Console sandbox kept → Console
B — Existing Platform account After hookdeck logout (guest fields cleared) device_name only Signin → CliAuth team picker CLI on existing Platform org; guest sandbox left on the original guest accountexisting path; regression-tested
C — Accidental new signup Same as B, user clicks Sign up on signin page (none — signin ticket) Signup → onboarding → CliAuth New Platform org; guest sandbox orphaned; CLI Guest Sandbox Abandoned on core — existing path; regression-tested
hookdeck listen
hookdeck login              # A: claim guest sandbox (default)

hookdeck logout             # required for B — clears guest creds so login uses sign-in, not signup
hookdeck login              # B: sign in to existing Platform account (existing path)
# If you then click "Sign up" in the browser → C

Path B note: logoutlogin to attach the CLI to an existing Platform account is not new — it is how the CLI has always worked once guest credentials are cleared. This PR adds tests to ensure claim-path work does not break it. Without hookdeck logout first, the CLI still has guest credentials and the default sends you to signup (path A).

hookdeck login --cli-key (product onboarding)

Dashboard onboarding and Console CLI destination copy hookdeck login --cli-key <key> for claimed product keys (user + project set at creation).

  • ConfigureFromClaimedCliKey — validate via GET /cli-auth/validate, save config, clear stale guest profile fields. Does not start device login or guest claim.
  • Bug fixed: previously, a guest Console profile + --cli-key fell through to Login(), overwrote api_key with the onboarding key, then POST /cli-auth sent mismatched guest_user_id / guest_api_key → 401.
  • Guest upgrade tracking: plain hookdeck login (no --cli-key) still opens the browser for Journey A. --cli-key is for attaching claimed product keys (e.g. after browser signup + EG onboarding), not for replacing the guest claim flow.

Claim path details (A)

  • Valid guest API key does not skip the browser — CLI still calls StartLogin so the user can complete signup / signupGuest.
  • Stale guest API key (401 on validate): preserve credentials for the POST, clear the stored key, continue browser login with guest attestation (guest_user_id + guest_api_key so the server can prove the caller is that guest).

Existing Platform account (B) — regression coverage only

  • hookdeck logout clears guest_user_id, guest_url, and the stored API key (guest attestation is not sent on the next login).
  • Next hookdeck login sends device name only → Dashboard signin URL. No new CLI behaviour — acceptance tests assert the POST body with vs without guest profile.

Listen guest URL (separate from hookdeck login)

The TUI link to open Console in a browser (/signin/guest?token=…). This is not the hookdeck login claim flow.

  • RefreshGuestSigninLink on listen start and ~50m TUI tick → POST /cli/guest/signin-link (core endpoint).
  • Mints a fresh token for the same URL shape; destination after auth is unchanged (Console).

MCP hookdeck_login follows the same guest vs logged-out rules.


Code changes

  • pkg/login/client_login.go — guest credentials on POST; stale-key handling; logout clears guest fields
  • pkg/login/guest_link.go — refresh helper + listen integration
  • pkg/login/guest_link_test.go — refresh success, API error fallback, missing profile no-op
  • pkg/login/claimed_cli_key.go — explicit --cli-key validate-and-save (guest profile safe)
  • pkg/cmd/login.go — visible login --cli-key; routes claimed keys to dedicated path
  • pkg/hookdeck/request_log_redact.go — redact guest attestation in debug logs
  • README.md, AGENTS.md — CLI authentication keys reference + onboarding behaviour
  • test/acceptance/guest_login_acceptance_test.go — guest profile POST body vs after logout

Test plan

  • go test ./...
  • go test -tags=guest ./test/acceptance/... — guest profile POST body vs after logout
  • go test ./test/acceptance/... — stale validate 401 → browser flow (login_auth_acceptance_test.go, basic tag)
  • pkg/login/claimed_cli_key_test.go — guest profile + onboarding --cli-key
  • pkg/login/guest_link_test.go
  • Pair with core staging / L3 Playwright (local only — core CI skips guest-intent specs)

Depends on

core #5233: origin_guest_user_id, guest attestation (guest_user_id + guest_api_key on POST /cli-auth), POST /cli/guest/signin-link, CliAuth guest-claim completion, flow_mismatch intent API, PostHog identity sync.

leggetter and others added 8 commits June 22, 2026 16:29
Store guest_user_id, attest guest credentials on cli-auth login, and
refresh guest sign-in URLs on listen start and via a TUI ticker.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add claim_guest, login, and create_new intents with TTY prompt, Cobra flags,
and auth_intent on POST /cli-auth. MCP login defaults to claim_guest with
guest credentials preserved on stale 401.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add guest-tagged acceptance tests and CI matrix slice for auth_intent on
POST /cli-auth.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Guest profiles always claim the Console sandbox on login. Use hookdeck
logout then hookdeck login to attach the CLI to an existing Platform
account. Remove the TTY three-way prompt.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Guest profile presence alone selects claim_guest; drop Options and intent
flags from the login command surface.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
When guest_url is set, a valid API key no longer short-circuits login.
Preserve stale guest key on validate 401 and send guest credentials on
POST /cli-auth. Remove auth_intent from StartLogin payload.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add unit test for guest profile with valid key reaching signup claim flow.
Update guest acceptance test to assert guest_user_id/guest_api_key on POST
after validate 401 without auth_intent.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Cover profile update on success, fallback to saved URL on API error,
and no-op when guest profile fields are missing.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@leggetter leggetter changed the title feat(cli): persist guest lineage and refresh sign-in links feat(cli): guest login credentials, stale-key handling, and sign-in link refresh Jun 23, 2026
@leggetter leggetter changed the title feat(cli): guest login credentials, stale-key handling, and sign-in link refresh feat: guest login credentials, stale-key handling, and sign-in link refresh Jun 23, 2026
leggetter and others added 3 commits June 23, 2026 14:07
Guard empty link responses, defer refresh to async TUI path, unify
persisting refresh through login.RefreshGuestSigninLink, and fix guest
API error formatting.

Co-authored-by: Cursor <cursoragent@cursor.com>
Redact guest_api_key from PerformRequest debug bodies and Authorization
from debug headers. Write hookdeck login --local config as 0600.

Co-authored-by: Cursor <cursoragent@cursor.com>
Product onboarding keys must configure the CLI even when a guest Console
profile is still on disk; use a dedicated validate-and-save path instead
of falling through to POST /cli-auth with mismatched guest credentials.

Also document CLI authentication keys, expose login --cli-key in help,
and include guest tests in acceptance slice 0.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to 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.

1 participant