Skip to content

fix: reject project list/use for CI-scoped keys with clear errors#308

Open
leggetter wants to merge 4 commits into
mainfrom
fix/cli-project-list-ci-key
Open

fix: reject project list/use for CI-scoped keys with clear errors#308
leggetter wants to merge 4 commits into
mainfrom
fix/cli-project-list-ci-key

Conversation

@leggetter

Copy link
Copy Markdown
Collaborator

Summary

  • Validate credentials before project list / project use and fail fast with ErrCIScopedCredentials when the stored key has no user_id (e.g. after hookdeck ci)
  • Call ListProjects without X-Team-ID so stale project_id / legacy workspace fields do not scope the teams request
  • hookdeck login: CI keys no longer short-circuit validate — TTY opens browser sign-in; non-TTY fails immediately (no poll)
  • MCP: hookdeck_login treats CI-scoped keys as needing browser auth; hookdeck_projects surfaces reauth hints for local and API errors
  • SaveProfile strips deprecated workspace_* and team_* keys from config on write

Context

hookdeck project list requires a user-associated CLI key. Keys created by hookdeck ci are scoped to one project and previously caused an opaque API 500 on GET /teams. This PR adds client-side guards with actionable messages and aligns login/MCP behavior so users can upgrade to a full session.

Pairs with the companion API PR in hookdeck/core (CLI_USER_REQUIRED on GET /teams).

Test plan

  • go test ./...
  • go test -tags=project_use ./test/acceptance/... -run TestProjectListFailsWithCIKey
  • hookdeck ci then project list → clear error, not Fatal Error / 500
  • hookdeck login with CI key in TTY → browser sign-in; headless → immediate error
  • MCP with CI key → hookdeck_login returns browser URL (not "Already authenticated")

Made with Cursor

Validate credentials before cross-project commands, omit X-Team-ID on
ListProjects, improve login and MCP flows for single-project CI keys,
and strip legacy workspace/team keys on profile save.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@leggetter leggetter changed the title fix(cli): reject project list/use for CI-scoped keys with clear errors fix: reject project list/use for CI-scoped keys with clear errors Jun 26, 2026
@leggetter

Copy link
Copy Markdown
Collaborator Author

Review

Solid change, well-tested (unit + MCP + acceptance), and the fix lands at a reasonable depth — client-side ValidateAPIKey guards plus stripping X-Team-ID/X-Project-ID from ListProjects so a stale project_id can't scope GET /teams. Findings below are minor; nothing is a correctness blocker.

1. pkg/gateway/mcp/tool_login.go:125 — "scoped to one project" prefix is also shown for invalid/expired keys

In the if client.APIKey != "" block, an unauthorized (revoked/expired) key satisfies neither early-return condition (err != nil && !IsUnauthorizedError(err) is false; err == nil && !lacks_user is false), so it falls through with client.APIKey still set. loginPrefix then unconditionally sets "Current credentials are scoped to one project; opening browser sign-in for full access." — so a user whose key was revoked is incorrectly told it's "scoped to one project." Consider gating the prefix on the CI case (lacks_user == true) rather than just client.APIKey != "".

2. pkg/cmd/project_list.go — network round-trip runs before offline --type validation

EnsureUserAssociatedCredentials (an HTTP validate call) is inserted before the --type flag-value check. project list --type nonsense now incurs a network/auth call and can fail on auth before reporting the local flag error. Minor; cheap offline validation reads better placed first.

3. Extra validate round-trip per call (intended trade-off)

project list/use and MCP projectsList/projectsUse now issue a ValidateAPIKey in addition to ListProjects, and hookdeck_login's already-authenticated fast path always makes an HTTP call where it previously returned instantly. Inherent to detecting CI keys client-side, so acceptable — noting it as a conscious choice.

Notes (non-blocking): lacks_user is snake_case but consistent with existing locals in pkg/ (no linter config in repo); removeLegacyConfigKeys rebuilding viper via removeKey on every SaveProfile checks out (config path restored, managed keys preserved, existing-file perms not downgraded — mirrors RemoveProfile); ValidateCredentials is exported but only used internally.

#1 is the one I'd most want addressed before merge.


Generated by Claude Code

leggetter and others added 3 commits June 26, 2026 12:21
Only show CI-scoped login prefix when validate confirms no user_id; validate
--type before the credentials check so invalid flags fail offline (fixes CI).

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Rebalance CI matrix runtimes (~5.3 min slice 0 vs ~3 min slice 2). Move
ConnectionListResponse to helpers so gateway tests compile without
connection_list in the same build.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Rename ErrProjectScopedCredentials (ErrCIScopedCredentials alias), update
messages for single-project scope, and match CLI_PROJECT_SCOPED from API.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@leggetter leggetter marked this pull request as ready for review June 26, 2026 12:41
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