fix(tokens): Workspace Tokens tab 500 on 'global' sentinel (no node selected) #1415

Merged
devops-engineer merged 1 commits from fix/workspace-tokens-global-sentinel-500 into staging 2026-05-17 13:46:56 +00:00
Owner

Summary

Settings → Workspace Tokens returned GET /workspaces/global/tokens → 500 {"error":"failed to list tokens"} whenever opened with no canvas node selected. Token CREATE in that view broke the same way.

Root cause: SettingsPanel passes the literal sentinel "global" as the workspace id when no node is selected. The backend queries the uuid workspace_id column with it → Postgres invalid input syntax for type uuid: "global" → opaque 500. SecretsTab already handles the sentinel (api/secrets.ts reroutes "global"/settings/secrets); TokensTab did not — that asymmetry was the bug.

Pre-existing since 2026-04-13 — NOT a regression.

Workaround (until merged): select a workspace node before opening the tab, or use the Org API Keys tab.

Changes

Frontend (the user-visible fix)canvas/src/components/settings/TokensTab.tsx

  • TokensTab is now sentinel-aware exactly like SecretsTab. When workspaceId === 'global' it no longer calls /workspaces/global/tokens — it renders a clean state ("Select a workspace node first") that points the user at the Org API Keys tab (the existing org-wide surface). No 500, no scary error UI.
  • The red account "Error" in this view was just this 500 surfacing through TokensTab's local error banner (verified in code — there is no separate error widget tied to this call). It resolves with this guard.

Backend (defense-in-depth, same PR)workspace-server/internal/handlers/tokens.go

  • List / Create / Revoke validate c.Param("id") as a UUID up front and return 400 {"error":"invalid workspace id"} instead of leaking a DB type error as a 500. Mirrors the existing uuid.Parse guard in handlers/activity.go.
  • Added the missing log.Printf on the List query-error branch — it was the only token handler silently swallowing the DB error, which is why this incident had zero log trail.

Product note for CTO

There is no /workspaces/global/tokens endpoint — workspace tokens are inherently per-workspace; the org-wide equivalent is the separate Org API Keys tab (OrgTokensTab). So unlike SecretsTab (which reroutes to a real global-secrets endpoint), the lowest-risk safe behavior here is a disabled state + pointer to Org API Keys rather than a reroute. Flag if a different UX is wanted — this was the lowest-risk choice, not a hard product decision.

Test plan

  • go build ./... + go vet ./internal/handlers/ — clean
  • go test ./internal/handlers/ — full suite pass (incl. new non-UUID 400 table test asserting List/Create/Revoke short-circuit before any DB call)
  • Canvas tsc --noEmit — zero errors in production (non-test) code; changed component compiles clean
  • vitest run src/components/settings/__tests__/ — 183/183 pass, incl. new sentinel tests (no API call + Org-pointer rendered + no error banner)
  • Manual: open Settings → Workspace Tokens with NO node selected → sane state, no 500
  • Manual: select a real workspace node → tokens still list/create (200, unchanged)

🤖 Generated with Claude Code

## Summary Settings → Workspace Tokens returned `GET /workspaces/global/tokens → 500 {"error":"failed to list tokens"}` whenever opened with **no canvas node selected**. Token CREATE in that view broke the same way. **Root cause:** `SettingsPanel` passes the literal sentinel `"global"` as the workspace id when no node is selected. The backend queries the `uuid` `workspace_id` column with it → Postgres `invalid input syntax for type uuid: "global"` → opaque 500. `SecretsTab` already handles the sentinel (`api/secrets.ts` reroutes `"global"` → `/settings/secrets`); `TokensTab` did not — that asymmetry was the bug. **Pre-existing since 2026-04-13 — NOT a regression.** **Workaround (until merged):** select a workspace node before opening the tab, or use the **Org API Keys** tab. ## Changes **Frontend (the user-visible fix)** — `canvas/src/components/settings/TokensTab.tsx` - `TokensTab` is now sentinel-aware exactly like `SecretsTab`. When `workspaceId === 'global'` it no longer calls `/workspaces/global/tokens` — it renders a clean state ("Select a workspace node first") that points the user at the **Org API Keys** tab (the existing org-wide surface). No 500, no scary error UI. - The red account "Error" in this view was just this 500 surfacing through `TokensTab`'s local error banner (verified in code — there is no separate error widget tied to this call). It resolves with this guard. **Backend (defense-in-depth, same PR)** — `workspace-server/internal/handlers/tokens.go` - `List` / `Create` / `Revoke` validate `c.Param("id")` as a UUID up front and return `400 {"error":"invalid workspace id"}` instead of leaking a DB type error as a 500. Mirrors the existing `uuid.Parse` guard in `handlers/activity.go`. - Added the missing `log.Printf` on the `List` query-error branch — it was the only token handler silently swallowing the DB error, which is why this incident had **zero log trail**. ## Product note for CTO There is **no** `/workspaces/global/tokens` endpoint — workspace tokens are inherently per-workspace; the org-wide equivalent is the separate **Org API Keys** tab (`OrgTokensTab`). So unlike `SecretsTab` (which reroutes to a real global-secrets endpoint), the lowest-risk safe behavior here is a disabled state + pointer to Org API Keys rather than a reroute. Flag if a different UX is wanted — this was the lowest-risk choice, not a hard product decision. ## Test plan - [x] `go build ./...` + `go vet ./internal/handlers/` — clean - [x] `go test ./internal/handlers/` — full suite pass (incl. new non-UUID 400 table test asserting List/Create/Revoke short-circuit before any DB call) - [x] Canvas `tsc --noEmit` — zero errors in production (non-test) code; changed component compiles clean - [x] `vitest run src/components/settings/__tests__/` — 183/183 pass, incl. new sentinel tests (no API call + Org-pointer rendered + no error banner) - [ ] Manual: open Settings → Workspace Tokens with NO node selected → sane state, no 500 - [ ] Manual: select a real workspace node → tokens still list/create (200, unchanged) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hongming added 1 commit 2026-05-17 13:22:28 +00:00
fix(tokens): make Workspace Tokens tab sentinel-aware + reject non-UUID workspace id
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 2s
CI / Detect changes (pull_request) Successful in 4s
E2E API Smoke Test / detect-changes (pull_request) Successful in 8s
E2E Chat / detect-changes (pull_request) Successful in 10s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Harness Replays / detect-changes (pull_request) Successful in 5s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Successful in 4s
qa-review / approved (pull_request) Successful in 4s
security-review / approved (pull_request) Successful in 4s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 5s
sop-tier-check / tier-check (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Python Lint & Test (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Failing after 10s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m37s
Harness Replays / Harness Replays (pull_request) Successful in 2s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1m55s
CI / Platform (Go) (pull_request) Successful in 5m8s
CI / Canvas (Next.js) (pull_request) Successful in 6m20s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / all-required (pull_request) Successful in 0s
audit-force-merge / audit (pull_request) Successful in 3s
4fd6612272
Settings → Workspace Tokens 500'd whenever opened with no canvas node
selected. SettingsPanel passes the literal sentinel "global" as the
workspace id; the backend queries the uuid `workspace_id` column with
it → Postgres `invalid input syntax for type uuid: "global"` → opaque
500 ("failed to list tokens"). Token create in that view broke the same
way. SecretsTab already handles the sentinel (api/secrets.ts reroutes
"global" → /settings/secrets); TokensTab did not — that asymmetry was
the bug. Pre-existing since 2026-04-13, NOT a regression.

Frontend (user-visible fix): TokensTab is now sentinel-aware like
SecretsTab. When workspaceId === "global" (no node selected) it no
longer calls /workspaces/global/tokens — it renders a clean state
pointing the user to the Org API Keys tab (the existing org-wide
surface). No 500, no scary error banner. The red account "Error" in
this view was just this 500 surfacing through TokensTab's local error
banner; it resolves with this guard (verified in code — no separate
widget).

Backend (defense-in-depth, same PR): List/Create/Revoke validate
c.Param("id") as a UUID up front and return 400 {"error":"invalid
workspace id"} instead of leaking a DB type error as a 500. Added the
missing log.Printf on the List query-error branch — it was the only
token handler silently swallowing the DB error, which is why this
incident had zero log trail. Mirrors the uuid.Parse guard already in
handlers/activity.go.

Workaround (pre-merge): select a workspace node before opening the
tab, or use the Org API Keys tab.

Product note for CTO: there is no /workspaces/global/tokens endpoint
(workspace tokens are inherently per-workspace; the org-wide
equivalent is the separate Org API Keys tab), so — unlike SecretsTab
which reroutes to a real global-secrets endpoint — the lowest-risk
safe behavior was a disabled state + pointer to Org API Keys rather
than a reroute. Flag if a different UX is wanted.

Tests: added TokensTab sentinel tests (no API call + Org-pointer) and
a backend table test asserting List/Create/Revoke 400 on non-UUID id
without hitting the DB. Updated existing token handler tests to use
valid UUIDs (they used "ws-1" etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
core-be approved these changes 2026-05-17 13:25:09 +00:00
core-be left a comment
Member

Backend / Security lens — five-axis review

Reviewed commit 4fd66122 on PR #1415 (base staging). Author is hongming; I am a non-author reviewer (workspace-server / handlers owner).

1. Correctness. Root cause confirmed by reading the diff: workspace_id is a uuid column and c.Param("id") flowed unvalidated into QueryContext/ExecContext, so the "global" sentinel produced invalid input syntax for type uuid → opaque 500 (and the same for CREATE). validWorkspaceID (uuid.Parse) is applied at the top of List, Create, and Revoke before any DB access. 400 body {"error":"invalid workspace id"} matches the file's existing gin.H{"error":...} shape. Complete across all three user-facing routes.

2. Security. Net positive, no regression. Pre-filters malformed input before the DB driver, eliminating the type-error info leak and shrinking the input surface. Does NOT touch the WorkspaceAuth middleware gate — authz semantics unchanged; this is a pure input-shape guard. uuid.Parse is a fixed-grammar parser (no regex/ReDoS). The added log.Printf interpolates a UUID-shaped id + %v of the DB error — no token plaintext/hash logged, consistent with the file's existing logging discipline (tokens:-prefixed lines).

3. Observability. The missing log.Printf on the List query-error branch is added — this was the precise reason the incident had zero log trail (only token handler that silently swallowed the DB error). Format matches sibling lines in Create/Revoke.

4. Tests. TestTokenHandler_RejectsNonUUIDWorkspaceID is a proper table test over List/Create/Revoke asserting 400 + error body AND ExpectationsWereMet() with zero expectations registered — proving the guard short-circuits before any SQL (the meaningful invariant). Existing sqlmock tests correctly migrated "ws-1"→fixed valid UUID constants; TestTokenHandler_RevokeWrongWorkspace correctly repointed to a valid-but-different UUID so it still exercises the ownership-404 branch instead of collapsing into the new 400. No test weakened to pass. Verified intent against the prod handler routes (:id is the workspace param, :tokenId the token).

5. Maintainability / blast radius. Surgical: one helper + three 4-line guards + one log line; mirrors the existing uuid.Parse precedent in handlers/activity.go (cited in the doc comment). Zero contract change for valid-UUID callers (200/201/200 paths byte-identical). Correctly identified as pre-existing since 2026-04-13, not a regression.

Verdict: APPROVE (backend/security axes). No blocking findings. Optional non-blocking nit (do not gate): the three identical guard blocks could be a one-line middleware on the wsAuth group, but inline matches surrounding handler style and is clearer for a hotfix.

**Backend / Security lens — five-axis review** Reviewed commit `4fd66122` on PR #1415 (base `staging`). Author is `hongming`; I am a non-author reviewer (workspace-server / handlers owner). **1. Correctness.** Root cause confirmed by reading the diff: `workspace_id` is a `uuid` column and `c.Param("id")` flowed unvalidated into `QueryContext`/`ExecContext`, so the `"global"` sentinel produced `invalid input syntax for type uuid` → opaque 500 (and the same for CREATE). `validWorkspaceID` (`uuid.Parse`) is applied at the top of `List`, `Create`, and `Revoke` before any DB access. 400 body `{"error":"invalid workspace id"}` matches the file's existing `gin.H{"error":...}` shape. Complete across all three user-facing routes. **2. Security.** Net positive, no regression. Pre-filters malformed input before the DB driver, eliminating the type-error info leak and shrinking the input surface. Does NOT touch the `WorkspaceAuth` middleware gate — authz semantics unchanged; this is a pure input-shape guard. `uuid.Parse` is a fixed-grammar parser (no regex/ReDoS). The added `log.Printf` interpolates a UUID-shaped id + `%v` of the DB error — no token plaintext/hash logged, consistent with the file's existing logging discipline (`tokens:`-prefixed lines). **3. Observability.** The missing `log.Printf` on the `List` query-error branch is added — this was the precise reason the incident had zero log trail (only token handler that silently swallowed the DB error). Format matches sibling lines in `Create`/`Revoke`. **4. Tests.** `TestTokenHandler_RejectsNonUUIDWorkspaceID` is a proper table test over List/Create/Revoke asserting 400 + error body AND `ExpectationsWereMet()` with zero expectations registered — proving the guard short-circuits before any SQL (the meaningful invariant). Existing sqlmock tests correctly migrated `"ws-1"`→fixed valid UUID constants; `TestTokenHandler_RevokeWrongWorkspace` correctly repointed to a valid-but-different UUID so it still exercises the ownership-404 branch instead of collapsing into the new 400. No test weakened to pass. Verified intent against the prod handler routes (`:id` is the workspace param, `:tokenId` the token). **5. Maintainability / blast radius.** Surgical: one helper + three 4-line guards + one log line; mirrors the existing `uuid.Parse` precedent in `handlers/activity.go` (cited in the doc comment). Zero contract change for valid-UUID callers (200/201/200 paths byte-identical). Correctly identified as pre-existing since 2026-04-13, not a regression. **Verdict: APPROVE** (backend/security axes). No blocking findings. Optional non-blocking nit (do not gate): the three identical guard blocks could be a one-line middleware on the `wsAuth` group, but inline matches surrounding handler style and is clearer for a hotfix.
core-uiux approved these changes 2026-05-17 13:25:33 +00:00
core-uiux left a comment
Member

Frontend / UX lens — five-axis review

Reviewed commit 4fd66122 on PR #1415 (base staging). Author is hongming; I am a non-author reviewer (canvas / UX owner).

1. Correctness. SettingsPanel renders <TokensTab workspaceId={workspaceId} /> and passes the literal "global" sentinel when no canvas node is selected — confirmed against SettingsPanel.tsx and the api/secrets.ts:9 precedent. The fix early-returns a dedicated state when workspaceId === GLOBAL_WORKSPACE_ID BEFORE the useEffect/fetchTokens is mounted (the fetch lives in the extracted WorkspaceTokensTab, only rendered for real ids), so /workspaces/global/tokens is never called and handleCreate is unreachable in that state. This correctly kills both the list-500 and the create-500. Hooks ordering is safe — the guard is a top-level early return before any hook in the component that owns them (hooks moved into WorkspaceTokensTab), no conditional-hook violation.

2. UX. Sound. Instead of a red error banner, the empty state reuses the tab's existing visual language (same heading + text-ink-mid copy + py-6 centered block as the existing "No active tokens" state), and explicitly points the user to the Org API Keys tab — which is the actually-correct surface for org-wide keys (there is no global workspace-tokens concept server-side). No dead/disabled "+ New Token" button is shown in this state (verified by the new test asserting "New Token" is absent), avoiding a button that would 500 on click. The red account "Error" the user saw was this same 500 surfacing through the local error banner; with the fetch gone it no longer fires — correctly explained in the PR body, no separate widget involved (matches my read of the canvas).

3. Accessibility. The new state is plain semantic text (<h3> + <p>), no interactive traps, consistent contrast tokens (text-ink, text-ink-mid, text-accent) already used elsewhere in the panel. No focusable element that does nothing. Acceptable; no a11y regression.

4. Tests. New TokensTab — global sentinel describe block asserts (a) api.get/api.post NOT called, (b) the "Select a workspace node" + "Org API Keys" copy renders, (c) no .text-bad error banner, (d) no "New Token" button. These pin exactly the user-visible acceptance criteria. Existing 12 TokensTab cases for the real-workspace path are untouched and still pass (verified the real-id path still routes through WorkspaceTokensTab unchanged) — the 200 list/create behavior for a real UUID is byte-identical.

5. Maintainability. Minimal: an early-return guard + extraction of the existing body into WorkspaceTokensTab with zero logic change to that body. Mirrors the api/secrets.ts sentinel precedent and is documented with a comment pointing at it. Product-decision ambiguity (disabled-state vs reroute) is explicitly flagged for CTO in the PR with the lowest-risk option chosen — correct call given there is no /workspaces/global/tokens endpoint to reroute to.

Verdict: APPROVE (frontend/UX axes). No blocking findings.

**Frontend / UX lens — five-axis review** Reviewed commit `4fd66122` on PR #1415 (base `staging`). Author is `hongming`; I am a non-author reviewer (canvas / UX owner). **1. Correctness.** `SettingsPanel` renders `<TokensTab workspaceId={workspaceId} />` and passes the literal `"global"` sentinel when no canvas node is selected — confirmed against `SettingsPanel.tsx` and the `api/secrets.ts:9` precedent. The fix early-returns a dedicated state when `workspaceId === GLOBAL_WORKSPACE_ID` BEFORE the `useEffect`/`fetchTokens` is mounted (the fetch lives in the extracted `WorkspaceTokensTab`, only rendered for real ids), so `/workspaces/global/tokens` is never called and `handleCreate` is unreachable in that state. This correctly kills both the list-500 and the create-500. Hooks ordering is safe — the guard is a top-level early return before any hook in the component that owns them (hooks moved into `WorkspaceTokensTab`), no conditional-hook violation. **2. UX.** Sound. Instead of a red error banner, the empty state reuses the tab's existing visual language (same heading + `text-ink-mid` copy + `py-6` centered block as the existing "No active tokens" state), and explicitly points the user to the **Org API Keys** tab — which is the actually-correct surface for org-wide keys (there is no global workspace-tokens concept server-side). No dead/disabled "+ New Token" button is shown in this state (verified by the new test asserting "New Token" is absent), avoiding a button that would 500 on click. The red account "Error" the user saw was this same 500 surfacing through the local error banner; with the fetch gone it no longer fires — correctly explained in the PR body, no separate widget involved (matches my read of the canvas). **3. Accessibility.** The new state is plain semantic text (`<h3>` + `<p>`), no interactive traps, consistent contrast tokens (`text-ink`, `text-ink-mid`, `text-accent`) already used elsewhere in the panel. No focusable element that does nothing. Acceptable; no a11y regression. **4. Tests.** New `TokensTab — global sentinel` describe block asserts (a) `api.get`/`api.post` NOT called, (b) the "Select a workspace node" + "Org API Keys" copy renders, (c) no `.text-bad` error banner, (d) no "New Token" button. These pin exactly the user-visible acceptance criteria. Existing 12 TokensTab cases for the real-workspace path are untouched and still pass (verified the real-id path still routes through `WorkspaceTokensTab` unchanged) — the 200 list/create behavior for a real UUID is byte-identical. **5. Maintainability.** Minimal: an early-return guard + extraction of the existing body into `WorkspaceTokensTab` with zero logic change to that body. Mirrors the `api/secrets.ts` sentinel precedent and is documented with a comment pointing at it. Product-decision ambiguity (disabled-state vs reroute) is explicitly flagged for CTO in the PR with the lowest-risk option chosen — correct call given there is no `/workspaces/global/tokens` endpoint to reroute to. **Verdict: APPROVE** (frontend/UX axes). No blocking findings.
Member

[core-qa-agent] APPROVED — Go handlers tests 14/14 pass, TokensTab Canvas tests 14/14 pass. Fix: TokensTab guards against "global" sentinel (no canvas node selected) returning a graceful empty-state instead of calling /workspaces/global/tokens (which 500s). Regression tests added in both tokens_test.go and TokensTab.test.tsx. e2e: N/A — platform not running locally (see CI).

[core-qa-agent] APPROVED — Go handlers tests 14/14 pass, TokensTab Canvas tests 14/14 pass. Fix: TokensTab guards against "global" sentinel (no canvas node selected) returning a graceful empty-state instead of calling /workspaces/global/tokens (which 500s). Regression tests added in both tokens_test.go and TokensTab.test.tsx. e2e: N/A — platform not running locally (see CI).
Member

[core-security-agent] APPROVED — security-positive bug fix. TokensTab guards workspaceId === "global" literal (safe string, not user input) and renders guidance UI instead of calling /workspaces/global/tokens (which 500s on Postgres uuid check). Prevents error disclosure + improves UX. No exec, injection, or auth concerns. OWASP 0/1

[core-security-agent] APPROVED — security-positive bug fix. TokensTab guards workspaceId === "global" literal (safe string, not user input) and renders guidance UI instead of calling /workspaces/global/tokens (which 500s on Postgres uuid check). Prevents error disclosure + improves UX. No exec, injection, or auth concerns. OWASP 0/1
Member

Review: LGTM

Clean fix. Adding validWorkspaceID guard with uuid.Parse in List/Create/Revoke handlers prevents the invalid input syntax for type uuid Postgres error from leaking as an opaque 500. The fix is defensive and consistent with existing patterns (mirrors the same guard in activity.go).

Key observations:

  • validWorkspaceID is a simple pure function — easy to unit test
  • Test fixes are correct: updated RevokeWrongWorkspace to use valid UUID so it exercises the ownership branch (not the validation guard)
  • SQL mock tests updated to use real UUIDs instead of ws-1/ws-2 placeholders
  • Log added for List query failures — good observability improvement

No code changes requested.

**Review: LGTM** ✓ Clean fix. Adding `validWorkspaceID` guard with `uuid.Parse` in List/Create/Revoke handlers prevents the `invalid input syntax for type uuid` Postgres error from leaking as an opaque 500. The fix is defensive and consistent with existing patterns (mirrors the same guard in `activity.go`). Key observations: - `validWorkspaceID` is a simple pure function — easy to unit test - Test fixes are correct: updated `RevokeWrongWorkspace` to use valid UUID so it exercises the ownership branch (not the validation guard) - SQL mock tests updated to use real UUIDs instead of `ws-1`/`ws-2` placeholders - Log added for `List` query failures — good observability improvement No code changes requested.
infra-runtime-be reviewed 2026-05-17 13:42:16 +00:00
infra-runtime-be left a comment
Member

Review: APPROVED

Bug fix — APPROVED

Root cause is correctly identified: SettingsPanel passes the literal "global" sentinel as workspace id when no node is selected. The workspace_id column is UUID type — passing a non-UUID raises invalid input syntax for type uuid which leaked as an opaque 500.

Implementation — APPROVED

  • validWorkspaceID() helper mirrors the same guard pattern already used in activity.go — consistent with codebase style.
  • Applied to List, Create, and Revoke handlers before any DB call — correct short-circuit.
  • Better error message (400 instead of 500) and improved log line for List query failure.
  • Canvas TokensTab.tsx change is appropriate — sentinel handling belongs client-side.

Tests — APPROVED

  • Regression test TestTokenHandler_RejectsNonUUIDWorkspaceID covers all three handlers against "global" sentinel, asserts 400, and verifies no DB call was made — the mock.ExpectationsWereMet() check is a good proof of short-circuit.
  • Existing tests updated to use valid UUIDs (wsUUID1/2/3 constants) — the old non-UUID params would now fail at the guard, so this is a necessary cleanup.

CI — all green

Canvas (Next.js) build now passes on this PR — confirming the npm ci fix in #1411 resolves the earlier build failures. CI/all-required SUCCESS.

## Review: APPROVED ### Bug fix — APPROVED ✅ Root cause is correctly identified: `SettingsPanel` passes the literal `"global"` sentinel as workspace id when no node is selected. The `workspace_id` column is UUID type — passing a non-UUID raises `invalid input syntax for type uuid` which leaked as an opaque 500. ### Implementation — APPROVED ✅ - `validWorkspaceID()` helper mirrors the same guard pattern already used in `activity.go` — consistent with codebase style. - Applied to `List`, `Create`, and `Revoke` handlers before any DB call — correct short-circuit. - Better error message (400 instead of 500) and improved log line for `List` query failure. - Canvas `TokensTab.tsx` change is appropriate — sentinel handling belongs client-side. ### Tests — APPROVED ✅ - Regression test `TestTokenHandler_RejectsNonUUIDWorkspaceID` covers all three handlers against `"global"` sentinel, asserts 400, and verifies no DB call was made — the `mock.ExpectationsWereMet()` check is a good proof of short-circuit. - Existing tests updated to use valid UUIDs (wsUUID1/2/3 constants) — the old non-UUID params would now fail at the guard, so this is a necessary cleanup. ### CI — all green ✅ Canvas (Next.js) build now passes on this PR — confirming the `npm ci` fix in #1411 resolves the earlier build failures. CI/all-required SUCCESS.
devops-engineer merged commit 330f54d281 into staging 2026-05-17 13:46:56 +00:00
Sign in to join this conversation.
No Reviewers
6 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1415