Summary
When the profile already has an API key, hookdeck login only re-validates against GET /cli-auth/validate (no browser). A 401 currently surfaces as a raw API error plus a noisy ERROR log from PerformRequest. hookdeck whoami hits the same path. MCP tools map 401 to a short generic line without config path or clear recovery (hookdeck_login with reauth: true).
This issue tracks clearer user-facing messages, quieter logging for expected validate 401s, and consistent MCP recovery text—without changing the "existing key → verify first" behavior of hookdeck login (browser-always login can be a separate product decision).
Problem
pkg/login/client_login.go: If Profile.APIKey is set, login only calls ValidateAPIKey(). On failure, users see low-level errors and log noise.
pkg/cmd/whoami.go: Same validate call; same experience.
- MCP (
pkg/gateway/mcp/errors.go): 401 → "Authentication failed. Check your API key." with no file path or reauth guidance for the general case. Project listing already has extra copy in tool_projects_errors.go. hookdeck_login can report "Already authenticated" while the on-disk key is stale until the first failing API call—recovery copy should be consistent wherever 401 appears.
Proposed approach
1. Shared detection helper
In pkg/hookdeck (near APIError / IsNotFoundError):
IsUnauthorizedError(err error) bool — errors.As into *APIError, StatusCode == 401.
2. Quieter logs for validate-only requests
- Add a
Client flag parallel to SuppressRateLimitErrors, e.g. SuppressAuthFailureLogs.
- Set it on the shallow copy built in
clientForCLIAuthValidate() (pkg/hookdeck/auth.go).
- In
PerformRequest (pkg/hookdeck/client.go), when checkAndPrintError fails with 401 and the flag is set, log at Debug instead of Error (401 only; avoid masking unexpected 403s on other endpoints).
3. CLI: structured failure message
Add a small formatter (e.g. under pkg/login or pkg/cmd) that takes *config.Config + error:
When IsUnauthorizedError(err):
- Explain the CLI used credentials from the config file (no secret values printed).
- Print config file path from
viper.ConfigFileUsed() when known; otherwise mention --hookdeck-config / unknown path.
- Mention active profile (aligned with
whoami's "Using profile …").
- Next steps: confirm key in dashboard;
hookdeck logout then hookdeck login or hookdeck login -i; note that with a key on disk, plain login only re-verifies.
Wire after ValidateAPIKey() fails in:
pkg/login/client_login.go
pkg/cmd/whoami.go (the GetAPIClient().ValidateAPIKey() path)
Optional follow-up: drop redundant Profile.ValidateAPIKey() in whoami if it's only an empty-key check.
4. MCP: recovery paragraph
- Extend
TranslateAPIError or add TranslateAPIErrorWithRecovery(err, configPath string) in pkg/gateway/mcp/errors.go.
- On 401, append guidance: credentials loaded from config (include path when non-empty); invalid/revoked; use
hookdeck_login with reauth: true (or logout / browser login outside MCP).
- Thread
configPath from Server.cfg / ConfigFileUsed(); centralize usage (e.g. via wrapWithTelemetry or server.toolAPIError) so every tool doesn't duplicate strings.
- Deduplicate with
listProjectsFailureMessage in tool_projects_errors.go via a shared mcpAuthRecoveryHint(configPath) (or similar).
- Optionally tweak tool/help strings in
tool_help.go / tools.go so "invalid stored key" appears next to "narrow dashboard key" where relevant.
5. Optional later (separate decision)
hookdeck_login could validate the stored key and on 401 suggest reauth: true instead of "Already authenticated." That improves MCP but adds a network round-trip on every no-arg login—track separately if not in scope.
Tests
IsUnauthorizedError: APIError, wrapped errors, non-matching errors.
- Formatter: substrings / table for path, profile,
logout, login.
- MCP:
TranslateAPIError / recovery with and without configPath; keep tool_projects_errors_test.go in sync if hints merge.
Files (reference)
| Area |
Files |
| HTTP client |
pkg/hookdeck/client.go, pkg/hookdeck/auth.go |
| CLI |
New helper + pkg/login/client_login.go, pkg/cmd/whoami.go |
| MCP |
pkg/gateway/mcp/errors.go, server.go, tool_projects_errors.go, tests |
README: optional one-liner under Login only if we want public docs to match.
Summary
When the profile already has an API key,
hookdeck loginonly re-validates againstGET /cli-auth/validate(no browser). A 401 currently surfaces as a raw API error plus a noisy ERROR log fromPerformRequest.hookdeck whoamihits the same path. MCP tools map 401 to a short generic line without config path or clear recovery (hookdeck_loginwithreauth: true).This issue tracks clearer user-facing messages, quieter logging for expected validate 401s, and consistent MCP recovery text—without changing the "existing key → verify first" behavior of
hookdeck login(browser-always login can be a separate product decision).Problem
pkg/login/client_login.go: IfProfile.APIKeyis set, login only callsValidateAPIKey(). On failure, users see low-level errors and log noise.pkg/cmd/whoami.go: Same validate call; same experience.pkg/gateway/mcp/errors.go): 401 → "Authentication failed. Check your API key." with no file path orreauthguidance for the general case. Project listing already has extra copy intool_projects_errors.go.hookdeck_logincan report "Already authenticated" while the on-disk key is stale until the first failing API call—recovery copy should be consistent wherever 401 appears.Proposed approach
1. Shared detection helper
In
pkg/hookdeck(nearAPIError/IsNotFoundError):IsUnauthorizedError(err error) bool—errors.Asinto*APIError,StatusCode == 401.2. Quieter logs for validate-only requests
Clientflag parallel toSuppressRateLimitErrors, e.g.SuppressAuthFailureLogs.clientForCLIAuthValidate()(pkg/hookdeck/auth.go).PerformRequest(pkg/hookdeck/client.go), whencheckAndPrintErrorfails with 401 and the flag is set, log at Debug instead of Error (401 only; avoid masking unexpected 403s on other endpoints).3. CLI: structured failure message
Add a small formatter (e.g. under
pkg/loginorpkg/cmd) that takes*config.Config+error:When
IsUnauthorizedError(err):viper.ConfigFileUsed()when known; otherwise mention--hookdeck-config/ unknown path.whoami's "Using profile …").hookdeck logoutthenhookdeck loginorhookdeck login -i; note that with a key on disk, plainloginonly re-verifies.Wire after
ValidateAPIKey()fails in:pkg/login/client_login.gopkg/cmd/whoami.go(theGetAPIClient().ValidateAPIKey()path)Optional follow-up: drop redundant
Profile.ValidateAPIKey()inwhoamiif it's only an empty-key check.4. MCP: recovery paragraph
TranslateAPIErroror addTranslateAPIErrorWithRecovery(err, configPath string)inpkg/gateway/mcp/errors.go.hookdeck_loginwithreauth: true(or logout / browser login outside MCP).configPathfromServer.cfg/ConfigFileUsed(); centralize usage (e.g. viawrapWithTelemetryorserver.toolAPIError) so every tool doesn't duplicate strings.listProjectsFailureMessageintool_projects_errors.govia a sharedmcpAuthRecoveryHint(configPath)(or similar).tool_help.go/tools.goso "invalid stored key" appears next to "narrow dashboard key" where relevant.5. Optional later (separate decision)
hookdeck_logincould validate the stored key and on 401 suggestreauth: trueinstead of "Already authenticated." That improves MCP but adds a network round-trip on every no-arg login—track separately if not in scope.Tests
IsUnauthorizedError:APIError, wrapped errors, non-matching errors.logout,login.TranslateAPIError/ recovery with and withoutconfigPath; keeptool_projects_errors_test.goin sync if hints merge.Files (reference)
pkg/hookdeck/client.go,pkg/hookdeck/auth.gopkg/login/client_login.go,pkg/cmd/whoami.gopkg/gateway/mcp/errors.go,server.go,tool_projects_errors.go, testsREADME: optional one-liner under Login only if we want public docs to match.