Skip to content

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

Merged
leggetter merged 18 commits into
mainfrom
feat/cli-guest-tracking-fixes
Aug 11, 2026
Merged

leggetter merged 18 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 account — existing 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: logout → login 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>
alexbouchardd and others added 5 commits July 3, 2026 14:31
Refresh calls POST /cli/guest with link_context only; empty device_name
was rejected by server Joi validation (422). Add omitempty and assert the
key is absent in unit and guest acceptance tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Merge hookdeck-cli #308 (EnsureUserAssociatedCredentials, CI login UX,
ListProjects header fix) while preserving guest claim flow on login.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ixes

Bring #308 handling into simplify guest flow: CI keys fail fast on
project list/use, login rejects headless CI keys, keep waitForGuestUpgrade
for active guest profiles.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@leggetter

Copy link
Copy Markdown
Collaborator Author

@alexbouchardd — Your simplify guest-login stack (#309) has been merged into this branch (feat/cli-guest-tracking-fixes). Please review the combined PR here when you have a chance; this is now the canonical place for the simplified approach.

leggetter and others added 2 commits August 4, 2026 22:27
Resolve const-block conflict in pkg/listen/tui/model.go: keep the
guest-URL refresh interval (this branch) alongside main's copy-feature
constants (detailsInstructions, copyStatusTimeout). Union merge; no
logic change. go build ./... and go vet pass.
Resolves the test-acceptance.yml conflict in favour of main's reusable
workflow (#328), and carries the branch's `guest` build tag into the
reusable acceptance.yml so guest_login_acceptance_test.go keeps running.

Co-Authored-By: Claude <noreply@anthropic.com>
@leggetter
leggetter marked this pull request as ready for review August 11, 2026 14:49
@leggetter
leggetter merged commit 244b068 into main Aug 11, 2026
12 checks passed
@leggetter
leggetter deleted the feat/cli-guest-tracking-fixes branch August 11, 2026 14:55
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
hookdeck 2.5.0

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre>## Summary

This release makes the CLI safe to run where there is no human: CI, Docker, `nohup`, and AI agents. Nine fixes, most sharing one failure shape — the CLI would silently do something other than what you asked, and the first symptom was missing traffic rather than an error. `hookdeck listen` now works headlessly and honours `HOOKDECK_API_KEY`, `--local` no longer touches your global config, and empty secrets are rejected instead of quietly producing a source that verifies nothing.

It also ships the guest login improvements merged after v2.4.0.

## New features

- **`hookdeck listen` reads `HOOKDECK_API_KEY`.** Running the CLI in CI no longer needs a separate `hookdeck ci` step — export a Project API key and `listen` exchanges it for CLI credentials, saves them, and connects to your project:

  ```sh
  export HOOKDECK_API_KEY="your-project-api-key"
  hookdeck listen 3000 shopify orders
  ```

  Precedence is `--cli-key`, then your stored login, then `HOOKDECK_API_KEY`. An existing login is never repointed by the environment — but a temporary guest profile is, so a machine that once ran `listen` without credentials still picks up your project when the variable is set. Replacing a guest profile prints a notice with the sandbox URL rather than swapping accounts silently.

- **`hookdeck listen` detects when there is no terminal** and uses compact output automatically, so it works under CI, Docker, `nohup`, and coding agents with no flags. Pass `--output compact` or `--output quiet` to get the same behaviour on a machine that does have a terminal.

## Fixes

Three of these turn a silent success into a clear failure. Nothing was removed or renamed, but if you have automation that depended on the old behaviour, these are the ones to check.

- **`hookdeck listen` no longer dies at startup without a terminal.** It defaulted to a full-screen UI that opens `/dev/tty`; anywhere without one it failed with `could not open a new TTY` and **exited 0 having forwarded nothing** — no tunnel, no error, nothing to attribute the failure to. ([#333](hookdeck/hookdeck-cli#333), [#339](hookdeck/hookdeck-cli#339))

- **`hookdeck listen` no longer silently creates a guest account when `HOOKDECK_API_KEY` is set.** Events still arrived on your machine, so it looked like it worked — but they went to a throwaway account with no connection, delivery history, retries, or issue triggers, not the project you configured. ([#334](hookdeck/hookdeck-cli#334))

- **`hookdeck ci --local` and `hookdeck login --local` no longer rewrite your global config.** `--local` added a second write rather than redirecting the first, so it silently switched the active project for every other `hookdeck` command on the machine — the opposite of what the flag is for. ([#332](hookdeck/hookdeck-cli#332))

- **Empty secret and identity flags are now rejected.** An unexported shell variable expands to an empty string, and the CLI accepted it: `hookdeck gateway source create --type STRIPE --webhook-secret "$UNSET_VAR"` produced a source with **no verification at all** while looking configured, so it rejected every genuinely signed request. `--webhook-secret ""`, `--source-webhook-secret ""`, `--name ""` and similar now error with a message naming the likely cause. To clear verification deliberately, use the JSON escape hatch: `--config '{"auth": null}'`. ([#335](hookdeck/hookdeck-cli#335))

- **`delete` and `dismiss` no longer exit 0 without doing anything.** Without a terminal these commands skipped the confirmation prompt, printed `Deletion cancelled.` and exited 0 — so a CI job deleted nothing and reported success. They now exit non-zero and tell you to pass `--force`. ([#338](hookdeck/hookdeck-cli#338))

- **Unauthenticated commands no longer hang for four minutes.** Any command run without credentials dropped into interactive sign-in, blocking on a prompt and opening a browser even on a machine with no display. Without a terminal it now exits immediately with instructions for `hookdeck ci`, `HOOKDECK_API_KEY` and `hookdeck login --cli-key`. ([#337](hookdeck/hookdeck-cli#337))

## Improvements / behavior changes

- **Guest login improvements** (merged after v2.4.0): guest credentials are now persisted and attested on login, guest sign-in links refresh while `listen` runs, and a stale key is preserved rather than discarded when validation returns 401. Note one behaviour change: with a guest profile, `hookdeck login` now always opens the browser to claim your sandbox instead of short-circuiting on a valid key. To attach the CLI to an existing Hookdeck account, run `hookdeck logout` then `hookdeck login`. ([#306](hookdeck/hookdeck-cli#306))

- README and `hookdeck listen --help` now describe running in CI accurately. The previous CI example showed a full-screen UI as the expected output while documenting an environment variable `listen` ignored — the exact combination the two bugs above disprove.

## Internal / reliability / infrastructure

- The root `--cli-key` flag is now hidden, matching `--api-key` beside it. Authentication is command-specific — `hookdeck login --cli-key`, `hookdeck listen --cli-key`, `hookdeck ci --api-key` — and those remain documented. The global flag still works for anyone already passing it; it is simply no longer advertised as a global option. It was added in #306 and never shipped in a release, so nothing that worked in v2.4.0 changes.
- Dropped `go-github` v28, whose only use was a single unauthenticated release check and whose only other effect was linking `golang.org/x/crypto/openpgp` — the unmaintained package flagged by GO-2026-5932, for which no fixed version exists. Replaced with a direct `net/http` call, which is now covered by tests for the first time. ([#331](hookdeck/hookdeck-cli#331))
- Added a `govulncheck` job to CI, so a clean scan is enforced rather than asserted once.
- Fixed the release skill's CI gate, which read the legacy commit-status API that GitHub Actions never writes and so reported `pending` unconditionally, blocking every release. ([#336](hookdeck/hookdeck-cli#336))

**Full Changelog**: hookdeck/hookdeck-cli@v2.4.0...v2.5.0









</pre>
  <p>View the full release notes at <a href="https://github.com/hookdeck/hookdeck-cli/releases/tag/v2.5.0">https://github.com/hookdeck/hookdeck-cli/releases/tag/v2.5.0</a>.</p>
</details>
<hr>

See merge request: Harmonybrew/homebrew-core!16506
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.

2 participants