Skip to content

fix(deployment): coerce the provider's null lease-status arrays at the query boundary - #3616

Merged
baktun14 merged 1 commit into
mainfrom
fix/deployment-null-lease-status-arrays
Aug 19, 2026
Merged

fix(deployment): coerce the provider's null lease-status arrays at the query boundary#3616
baktun14 merged 1 commit into
mainfrom
fix/deployment-null-lease-status-arrays

Conversation

@baktun14

@baktun14 baktun14 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Why

The redesigned Details tab blanked out with TypeError: Cannot read properties of null (reading 'map') for any deployment whose services expose no URIs. A deployment declaring only RANDOM_PORT endpoints hits it every time.

Providers marshal Go nil slices as JSON null, so such a service arrives as uris: null. The endpoint converters guarded with default parameters, which only substitute for undefined:

export function toUriLinks(uris: string[] = []): EndpointLink[] {
  return uris.map(...)   // null.map throws
}

Nothing flagged the unsafe map because LeaseStatusDto claimed uris was always string[] and typed ips as any. The rest of the codebase already knew better: DeploymentDetailHeader writes service.uris ?? [], DeploymentName writes (service.uris || []).map(...), and DeploymentName.spec.tsx has had a uris: null case for a while.

Ref CON-822

What

Rather than widen uris, forwarded_ports, and ips to | null and add a guard at every call site, this normalizes the nulls once in the lease-status queryFn. LeaseStatusDto is now a type that is actually true instead of one that needs guarding, so no future caller can re-arm the trap.

  • normalizeLeaseStatus coerces a nil map to {} and a nil slice to [] for all three collections. LeaseStatusResponse describes the wire shape (nullable) and LeaseStatusDto describes what consumers get (never null).
  • ForwardedPort and ServiceIp are now declared once in useLeaseQuery.ts. forwarded_ports previously carried an inline duplicate of the former and ips was any.
  • The three converters and PlacementServiceRowProps accept | null as defense in depth, so an un-normalized caller degrades to an empty list instead of throwing.
  • Normalizing in queryFn rather than in select keeps the coerced value cached per fetch, so React Query's structural sharing holds the reference stable across the 30s status refetch. That avoids the hand-rolled referential-stability dance omitAttestationSidecar needs, and the caller-supplied select contract is unchanged.

Forwarded ports and IPs are covered on the same path as URIs, at both the map level and the per-service level. Two smaller things fall out of this:

  • Object.keys(leaseStatus.services) in PlacementCard and LeaseRow was unguarded and would have thrown the same way on a nil services map. It can't now.
  • LeaseRow.tsx, DeploymentName.tsx, and DeploymentDetailHeader.tsx are untouched and still compile. Their existing guards are redundant after this, but harmless, and leaving them alone keeps this diff off a component with no test coverage.

I swept the rest of the redesigned detail page for the same class of bug and found nothing else. The other provider-proxy consumers on the page (attestation evidence, TEE carve-outs) read chain data or already-guarded arrays. Chain REST goes through grpc-gateway, which emits [] for an empty repeated field rather than null, which is why this only ever bit the provider path.

Tests

  • PlacementServiceRow.spec.tsx renders with uris, forwardedPorts, and ips all null and expects the None placeholder. This one reproduced the reported stack trace before the fix.
  • useLeaseQuery.spec.tsx covers the query boundary end to end plus normalizeLeaseStatus directly, including a nil services map and a service named __proto__.
  • ServiceEndpoints.spec.tsx covers each converter with null and undefined.

Verification

  • 3321 unit tests pass across 348 files.
  • tsc --noEmit reports the same 88 pre-existing errors before and after, with zero delta and none in the touched files.
  • eslint --quiet is clean.
  • /deployments/[dseq]/preview compiles under Turbopack, confirming the new type-only import across the query and component boundary is erased as expected.

Summary by CodeRabbit

  • Bug Fixes

    • Improved deployment details handling when endpoint data is missing or unavailable.
    • Empty URI, forwarded port, and IP collections now display consistently, including a “None” placeholder in expanded placement details.
    • Lease status data is normalized to prevent missing endpoint information from affecting the interface.
  • Tests

    • Added coverage for null, undefined, and empty endpoint collections, including edge-case service names.

…e query boundary

The redesigned Details tab blanked with "Cannot read properties of null
(reading 'map')" for any deployment whose services expose no URIs, such as
one declaring only RANDOM_PORT endpoints.

Providers marshal Go nil slices and maps as JSON null, so such a service
arrives as uris: null. The endpoint converters guarded with default
parameters, which only substitute for undefined, and LeaseStatusDto claimed
uris was always string[] (and typed ips as any), so nothing flagged the
unsafe map.

Normalize the nulls in the lease-status queryFn instead of widening the type
through every call site. LeaseStatusDto's arrays are now true rather than
merely guarded, existing consumers keep their guards and compile untouched,
and the unguarded Object.keys(leaseStatus.services) calls stop being a latent
crash too.
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf36d495-cfe3-4c30-af40-3e658437d967

📥 Commits

Reviewing files that changed from the base of the PR and between e11491a and 0327192.

📒 Files selected for processing (6)
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/PlacementServiceRow.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/PlacementServiceRow.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/ServiceEndpoints.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/ServiceEndpoints.tsx
  • apps/deploy-web/src/queries/useLeaseQuery.spec.tsx
  • apps/deploy-web/src/queries/useLeaseQuery.ts

Included review availability: 1 review is currently available. Your included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.


📝 Walkthrough

Walkthrough

The lease query now normalizes nullable provider endpoint collections into typed empty records and arrays. Endpoint converters and placement rows accept nullable values. Tests cover normalization, prototype-safe service keys, link conversion, and empty-state rendering.

Changes

Nullable lease endpoint handling

Layer / File(s) Summary
Lease status types and normalization
apps/deploy-web/src/queries/useLeaseQuery.ts, apps/deploy-web/src/queries/useLeaseQuery.spec.tsx
Added nullable wire-format types and normalizeLeaseStatus. Nullable maps and per-service endpoint collections become empty records or arrays.
Lease query integration
apps/deploy-web/src/queries/useLeaseQuery.ts, apps/deploy-web/src/queries/useLeaseQuery.spec.tsx
useLeaseStatus normalizes fetched LeaseStatusResponse data before returning it.
Endpoint conversion and placement rendering
apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/*
Endpoint converters and placement props accept nullable collections. Tests verify empty links and the “None” display.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 03271

The change normalizes nullable deployment status arrays at the query boundary and adds focused regression coverage; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

Suggested reviewers: stalniy

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deployment-null-lease-status-arrays

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/PlacementServiceRow.spec.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/PlacementServiceRow.tsx

ESLint skipped: the matched ESLint configuration already failed (missing-dependency).

apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentPlacements/ServiceEndpoints.spec.tsx

ESLint skipped: the matched ESLint configuration already failed (missing-dependency).

  • 3 others

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.12%. Comparing base (e11491a) to head (0327192).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3616      +/-   ##
==========================================
- Coverage   77.84%   77.12%   -0.73%     
==========================================
  Files        1171     1078      -93     
  Lines       30586    28154    -2432     
  Branches     7645     7174     -471     
==========================================
- Hits        23811    21714    -2097     
+ Misses       5967     5662     -305     
+ Partials      808      778      -30     
Flag Coverage Δ *Carryforward flag
api 89.31% <ø> (ø) Carriedforward from e11491a
deploy-web 68.91% <100.00%> (+0.01%) ⬆️
log-collector ?
notifications 93.90% <ø> (ø) Carriedforward from e11491a
provider-console 81.38% <ø> (ø) Carriedforward from e11491a
provider-inventory ?
provider-proxy 88.17% <ø> (ø) Carriedforward from e11491a
tx-signer ?

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...etail/DeploymentPlacements/PlacementServiceRow.tsx 100.00% <ø> (ø)
...ntDetail/DeploymentPlacements/ServiceEndpoints.tsx 94.11% <100.00%> (ø)
apps/deploy-web/src/queries/useLeaseQuery.ts 96.87% <100.00%> (+0.26%) ⬆️

... and 94 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Straightforward, well-tested null-safety fix that normalizes the provider's nullable lease-status collections at the query boundary.

Extended reasoning...

Overview

The PR fixes a crash (TypeError: Cannot read properties of null (reading 'map')) that occurs when a provider reports a service with no URIs, since Go marshals nil slices/maps as JSON null. The fix normalizes forwarded_ports, ips, and each service's uris to empty collections once in useLeaseStatus's queryFn (via a new normalizeLeaseStatus function), replacing the previous ad-hoc default-parameter guards (which only cover undefined, not null) in ServiceEndpoints.tsx. It also deduplicates the ForwardedPort/ServiceIp type declarations into useLeaseQuery.ts and tightens ips from any to a real type. The three converter functions and PlacementServiceRowProps additionally accept | null as defense in depth.

Security risks

None. This is purely defensive null-handling on read-only display data (lease status endpoints/ports/IPs shown in the UI). No auth, injection, or data-exposure surface is touched. I verified the __proto__-keyed-service test case: lodash/mapValues iterates via Object.keys/baseAssignValue, which safely treats __proto__ as an own property rather than reassigning the prototype, consistent with the added test.

Level of scrutiny

Moderate-low. The change touches a shared query hook (useLeaseStatus) consumed by several components (LeaseRow, PlacementCard, DeploymentLogs, DeploymentLeaseShell), but the change is additive/normalizing rather than behavior-changing for well-formed data — arrays that were already non-null pass through mapValues unchanged. I confirmed the description's claim that Object.keys(leaseStatus.services) is used unguarded in multiple call sites, all of which now benefit from the fix without being touched directly. omitAttestationSidecar, which composes with the normalized result in select, is generic over Record<string, V> | null | undefined and unaffected by the type tightening.

Other factors

Test coverage is thorough and targeted: ServiceEndpoints.spec.tsx covers each converter with null/undefined, useLeaseQuery.spec.tsx covers normalizeLeaseStatus directly (including nil-map and nil-slice cases and the __proto__ edge case), and PlacementServiceRow.spec.tsx reproduces the original crash and confirms the fix end-to-end. The bug-hunting system found no issues, and the one candidate raised (a null individual service entry crashing normalizeLeaseStatus) was examined and refuted. The diff is self-contained to the query/component pair described and doesn't touch CODEOWNER-sensitive or security-critical code.

@baktun14
baktun14 added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main with commit d51f3f6 Aug 19, 2026
58 checks passed
@baktun14
baktun14 deleted the fix/deployment-null-lease-status-arrays branch August 19, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant