Skip to content

fix(api): never send push_commands as null; guard it in the console - #1

Merged
jsmith212 merged 3 commits into
mainfrom
fix/snippet-push-commands-null
Sep 3, 2026
Merged

jsmith212 merged 3 commits into
mainfrom
fix/snippet-push-commands-null

Conversation

@jsmith212

Copy link
Copy Markdown
Owner

Problem

On /o/{org}/p/{project}/snippets, selecting any tile except Yocto hangs on the skeleton placeholder with

Uncaught TypeError: can't access property "length", (intermediate value)(...).push_commands is null

Every non-yocto builder (and yocto on a project with no sstate/downloads backend) leaves snippetContent.pushCommands nil. encoding/json marshals a nil slice as null, and the page did response.push_commands.length unguarded — files and env already had ?? [] guards, this one didn't. The server returned 200 every time, so nothing in the logs pointed at it.

Reproduced in v0.1.0 against a project with a configured bazel backend; the new test's failure output before the fix is literally the user-visible body:

{"tool":"bazel",...,"push_commands":null,"files":[{"path":".bazelrc",...}],"preview":true}

Fix

  • Server — snippetResponse normalises nil → []string{}, so push_commands is an array on the wire for every tool and both the minted and preview branches (same convention newProject uses for backends).
  • Console — push_commands is read through a ?? [] derived like files/env. The "Nothing to show" empty state now names the backend kind the selected tool needs and links to the project's backends page, so a project without that backend gets told what to do instead of a generic shrug.
  • Test — TestSnippetPushCommandsIsAnArrayOnTheWire asserts the JSON body contains "push_commands":[] and never null, for bazel (minted + preview), docker, and yocto-with-no-backends. The existing len(out.PushCommands) != 0 checks pass on nil, which is why this slipped through; the tscontract test also hand-builds PushCommands: []string{} and so never exercised the real path.

Verification

  • go test ./internal/api/ ok; new test fails on main, passes here
  • go vet ./..., go tool golangci-lint run ./internal/api/... — 0 issues
  • npm run check — 0 errors; npm test — 243 passed; npm run build ok

🤖 Generated with Claude Code

https://claude.ai/code/session_01NonmtCqhSy8f13VcK1rBMb

jsmith212 and others added 3 commits September 3, 2026 11:54
Selecting any tile but yocto on the snippets screen threw
`TypeError: can't access property "length", ... push_commands is null`
and left the skeleton placeholder up forever. Every non-yocto builder --
and yocto on a project without sstate/downloads -- leaves the
PushCommands slice nil, which encoding/json marshals as `null`, and the
page read `.length` off it unguarded (files/env were already guarded).

- snippetResponse normalises nil to an empty slice, so push_commands is
  an array on the wire for every tool and both the minted and preview
  branches (same convention as newProject's backends).
- The page reads push_commands through a `?? []` derived, like files and
  env, and the "nothing to show" empty state now names the backend kind
  the selected tool needs and links to the project's backends page.
- TestSnippetPushCommandsIsAnArrayOnTheWire asserts the JSON body, not
  the decoded struct: `len(nil) == 0` let every existing len-based check
  pass while the wire carried null.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NonmtCqhSy8f13VcK1rBMb
Review findings on the parent commit, all in the reworked empty state:

- The "Configure backends" button linked /o/{org}/p/{project}/backends,
  a route that does not exist -- the remediation CTA landed on the
  not-found page. Now projectPath(...)/backends/new, the same target the
  overview page uses (ConsoleNav documents this exact trap).
- The copy asserted "there isn't one. Add it" for a state also reached
  when the backend exists but is disabled -- following it earned a 409
  from the (project_id, kind) unique. The copy now defers to the
  warnings, which carry the missing-vs-disabled distinction.
- The mint bar is now gated on the preview having content: minting on a
  backend-less tile created a real one-time secret with no blocks to
  bake it into, rendered above an empty state whose advice (pick the
  tile again) would wipe it. hasContent is one shared derivation, so the
  mint bar and the empty state cannot disagree.
- "a oci backend" -> "the oci backend" on four of nine tiles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YM78iXWsYNLXccdzKhQcx1
A bare getByText('live') matches every live token's status badge; the
seeded dev user's table holds one per prior run against a reused local
database, so the assertion is a strict-mode violation everywhere but
CI's fresh database. Same ambiguous-locator class as the robot-row fix
(5ed6dcd), same remedy: scope to the row the test just created.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YM78iXWsYNLXccdzKhQcx1
@jsmith212
jsmith212 merged commit 4b5dcc8 into main Sep 3, 2026
12 checks passed
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