Skip to content

YNU-915: return success on absent-resource GET endpoints#748

Merged
philanton merged 5 commits into
mainfrom
fix/get-endpoint-errors
May 19, 2026
Merged

YNU-915: return success on absent-resource GET endpoints#748
philanton merged 5 commits into
mainfrom
fix/get-endpoint-errors

Conversation

@philanton
Copy link
Copy Markdown
Contributor

@philanton philanton commented May 12, 2026

Summary

Four single-resource lookups (channels.v1.get_latest_state, channels.v1.get_home_channel, channels.v1.get_escrow_channel, app_sessions.v1.get_app_definition) used to respond with RespErr when the record was absent. That counted "expected absence" as a failure in rpc_requests_total{result="failed"}, skewing success-rate dashboards, and forced clients to parse error strings to distinguish "not found" from a real failure.

This PR makes absence a successful response: the relevant payload field is now optional/nullable. Handlers drop the nil → Fail branch. Go and TS SDK methods return nil/null on absence; callers explicitly check for nil instead of treating the error path as the "no channel yet" branch.

Notion: https://www.notion.so/yellow-net/Return-empty-response-instead-of-error-for-absent-resource-GET-endpoints-35d75b8feb0480bcba31d203a64bc208

Changes

  • pkg/rpc/api.go — pointerize Channel / State / Definition response fields, omitempty
  • nitronode/api/channel_v1/{get_latest_state,get_home_channel,get_escrow_channel}.go, app_session_v1/get_app_definition.go — drop nil → Fail, return success with empty payload
  • sdk/go/{channel,app_session}.go, cerebro/commands.go, sdk/go/examples/challenge/main.go — return (nil, nil) on absence; callers (Deposit, Withdraw, Transfer, Acknowledge, CloseHomeChannel, Checkpoint, SubmitAppSessionDeposit) handle nil explicitly
  • sdk/ts/src/{rpc/api.ts,client.ts} — return Promise<T | null>, drop redundant try/catch around getLatestState in high-level ops; explicit nil checks in closeHomeChannel / checkpoint / submitAppSessionDeposit
  • sdk/ts-compat/src/client.ts — nil-aware (getChannelData, asset scans, getAppDefinition, getEscrowChannel)
  • docs/api.yaml — mark 4 response fields optional: true, remove *_not_found errors
  • Tests: *_NotFound paths now assert success + nil payload; success paths add require.NotNil; new NotFound cases for get_latest_state and get_escrow_channel; TS public-API drift snapshot refreshed

Test plan

  • go test ./... passes
  • go vet ./... clean
  • cd sdk/ts && npm test passes (154/154)
  • cd sdk/ts-compat && npm test passes (34/34)
  • cd sdk/ts && npm run typecheck clean
  • cd sdk/ts-compat && npm run typecheck clean
  • Verify on a deployed nitronode: get_latest_state for a wallet with no state returns Resp with state omitted, rpc_requests_total{result="success"} increments

Compatibility

Wire-shape change: response payload fields become optional. SDK ≥ this PR works against new nitronode. Older SDKs see the response unchanged in the success case; they would only fail on absence if they assumed the field was always populated. Roll out nitronode first, then bump SDK consumers.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Query APIs now treat missing channels, states, and app definitions as successful "not found" (null) results instead of errors; SDKs surface nullable return values and only error on real failures.
  • Documentation

    • API docs updated to document omitted/optional response fields for not-found cases.
  • Tests

    • Added and updated tests across services and SDKs to verify null/absent responses and adjusted success/not-found assertions.
  • Examples

    • Examples updated to guard against absent state results.

Review Change Stack

Four single-resource lookups (get_latest_state, get_home_channel,
get_escrow_channel, get_app_definition) used to respond with RespErr
when the record was absent. That counted absence as a failure in
rpc_requests_total{result="failed"}, skewing success-rate dashboards,
and forced clients to parse error strings to distinguish "not found"
from a real failure.

Make absence a successful response: the relevant payload field is now
optional/nullable. Handlers drop the nil→Fail branch. Go and TS SDK
methods return nil/null on absence; callers explicitly check for nil
instead of treating the error path as the "no channel yet" branch.

- pkg/rpc/api.go: pointerize Channel/State/Definition fields
- nitronode/api: drop nil→Fail, return success with empty payload
- sdk/go, cerebro, examples: handle nil from lookup methods
- sdk/ts, sdk/ts-compat: return T | null, drop redundant try/catch
- docs/api.yaml: mark fields optional, remove *_not_found errors
- tests: NotFound paths assert success + nil payload
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f0aeda85-00de-4567-b909-62b82e114bd7

📥 Commits

Reviewing files that changed from the base of the PR and between 50f812e and bd38115.

⛔ Files ignored due to path filters (1)
  • sdk/ts/test/unit/__snapshots__/public-api-drift.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • docs/api.yaml
  • nitronode/api/channel_v1/get_escrow_channel.go
  • sdk/go/channel.go
  • sdk/ts-compat/src/client.ts
  • sdk/ts/src/client.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • nitronode/api/channel_v1/get_escrow_channel.go
  • sdk/go/channel.go
  • docs/api.yaml
  • sdk/ts/src/client.ts

📝 Walkthrough

Walkthrough

Handlers, RPC types, SDKs, clients, CLI, examples, tests, and docs were changed so absent channels/states/app-definitions are returned as nil/null in successful RPC responses and callers handle null vs RPC errors.

Changes

Nullable "not found" semantics for API queries

Layer / File(s) Summary
RPC response type contracts
pkg/rpc/api.go
Response types for home channel, escrow channel, latest state, and app definition are now pointer/optional with json:",omitempty" to represent absence as nil.
Backend handlers return nil for not-found cases
nitronode/api/app_session_v1/get_app_definition.go, nitronode/api/channel_v1/get_home_channel.go, nitronode/api/channel_v1/get_escrow_channel.go, nitronode/api/channel_v1/get_latest_state.go
Handlers removed explicit "not found" errors and build responses conditionally, allowing nil to flow through when resources are absent.
Backend handler tests validate nil responses
nitronode/api/*_test.go
Tests updated and new not-found tests added to assert successful responses with nil fields for absent resources.
API documentation updated for nullable responses
docs/api.yaml
API docs updated to mark channel, state, and definition response fields as optional/omitted when absent and to clear previous not-found error docs.
Go SDK query methods return nil for not-found
sdk/go/channel.go, sdk/go/app_session.go
SDK queries return (nil, nil) when RPC responses omit nested objects; transformations are skipped for nil results.
Go SDK operations handle nil state and distinguish errors
sdk/go/channel.go
High-level SDK ops (Deposit, Withdraw, Transfer, Acknowledge, Checkpoint, CloseHomeChannel) now propagate RPC errors from GetLatestState and treat nil state as the not-found signal for channel-creation logic; additional consistency guards added.
Go SDK tests & examples
sdk/go/client_test.go, sdk/go/examples/challenge/main.go
Test fixtures updated to use pointer payloads; example and tests guard against nil state and assert nil-response behaviors.
TypeScript RPC interfaces reflect nullable fields
sdk/ts/src/rpc/api.ts
TS RPC interfaces updated to make channel, state, and definition optional and nullable.
TypeScript SDK query methods return null for not-found
sdk/ts/src/client.ts
TS SDK methods getHomeChannel, getEscrowChannel, getLatestState, getAppDefinition return `Promise<...
TypeScript SDK operations handle null state and check errors
sdk/ts/src/client.ts
High-level TS operations now use null to represent not-found, avoid swallowing RPC errors, and add guards for missing signed state and inconsistent home-channel records; submitAppSessionDeposit guards against missing state.
TypeScript compat client defensive checks
sdk/ts-compat/src/client.ts
Compat client adds per-asset lookup guards, optional chaining for balances from nullable state, and explicit throws when inner client returns falsy values for app/escrow lookups.
TS tests updated for null scenarios
sdk/ts/test/unit/client.test.ts
Tests and helpers updated to return null for missing latest state; new tests added for GET-method absence and nil-state guard behavior.
Client test fixtures
pkg/rpc/client_test.go, sdk/go/client_test.go
Client tests register empty responses and assert decoded nested objects are nil; success fixtures switched to pointer payloads.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RPC_Server
  participant Store
  Client->>RPC_Server: channels.v1.get_home_channel(request)
  RPC_Server->>Store: GetHomeChannel(wallet, asset)
  Store-->>RPC_Server: (nil) // not found
  RPC_Server-->>Client: 200 { channel: <omitted> } // success with nil
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ready

Suggested reviewers

  • dimast-x
  • nksazonov
  • ihsraham

Poem

🐰 A query that once threw an error with a frown,
Now returns graceful nil when nothing's found,
No more deref crashes tumbling down—
Just check for absence, hop softly around!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: converting four GET endpoints to return success with omitted/null fields instead of errors when resources are absent.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/get-endpoint-errors

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

# Conflicts:
#	sdk/ts/test/unit/__snapshots__/public-api-drift.test.ts.snap
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/ts-compat/src/client.ts (1)

526-541: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t collapse full RPC lookup failures into a false “not found” in getChannelData.

If all getHomeChannel calls fail (e.g., transport/auth outage), this loop swallows every exception and ends with Channel ... not found, which is a wrong result and can trigger incorrect caller behavior.

🔧 Proposed fix
 async getChannelData(_channelId: string): Promise<any> {
     await this.ensureAssets();
+    let hadSuccessfulLookup = false;
+    let lastLookupError: unknown = null;
     for (const [, info] of this.assetsBySymbol) {
         try {
             const ch = await this.innerClient.getHomeChannel(this.userAddress, info.symbol);
+            hadSuccessfulLookup = true;
             if (ch && ch.channelId === _channelId) {
                 return {
                     channel: ch,
                     state: await this.innerClient.getLatestState(this.userAddress, info.symbol, false),
                 };
             }
-        } catch {
+        } catch (err) {
+            lastLookupError = err;
             // transient RPC failure for this asset — skip
         }
     }
+    if (!hadSuccessfulLookup && lastLookupError) {
+        throw lastLookupError instanceof Error
+            ? new Error(`[compat] failed to query channels: ${lastLookupError.message}`)
+            : new Error('[compat] failed to query channels');
+    }
     throw new Error(`Channel ${_channelId} not found`);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/ts-compat/src/client.ts` around lines 526 - 541, The loop in
getChannelData swallows all exceptions from innerClient.getHomeChannel and
always throws "Channel ... not found", which masks transport/auth failures;
modify getChannelData to track RPC errors while iterating assetsBySymbol (e.g.,
a variable like lastRpcError or a flag rpcErrorOccurred) and only suppress
per-asset errors for transient misses, but if any getHomeChannel call throws,
rethrow a meaningful error (or the first caught exception) instead of the
generic "not found"; keep ensureAssets and the success path (returning channel
and latest state via innerClient.getLatestState) unchanged, but replace the
unconditional throw at the end with logic that throws the tracked RPC error when
present, otherwise throw the original "Channel ... not found" error.
🧹 Nitpick comments (3)
pkg/rpc/client_test.go (1)

134-162: ⚡ Quick win

Add explicit nil-payload success cases for the nullable contract.

These updated fixtures cover non-nil pointer decoding, but the behavior change is specifically about successful responses with omitted/null payload fields. Please add table rows/tests where channel/state/definition is absent and assert require.NoError plus nil field values.

Also applies to: 164-185, 215-246, 299-324

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/rpc/client_test.go` around lines 134 - 162, Add explicit success tests
for nil/omitted nullable payloads: create additional subtests (or table rows)
for TestClientV1_ChannelsV1GetHomeChannel that register a
ChannelsV1GetHomeChannelResponse where Channel.State is nil and another where
Channel.State is non-nil but Channel.State.Definition is nil (i.e., omit those
fields), using registerSimpleHandlerV1 and the same request via
client.ChannelsV1GetHomeChannel; assert require.NoError(t, err) and
require.Nil(t, resp.Channel.State) or require.Nil(t,
resp.Channel.State.Definition) as appropriate. Apply the same pattern to the
other analogous client tests in this file (the other ranges referenced in the
review) so each test covers non-nil decoding and explicit nil/omitted payload
success cases.
sdk/go/client_test.go (1)

20-218: ⚡ Quick win

Add explicit nil-payload tests for new not-found semantics.

These updates validate pointer payloads on happy paths, but this PR’s behavioral contract is “absent resource returns (nil, nil).” Please add cases for GetHomeChannel, GetEscrowChannel, GetLatestState, and GetAppDefinition where RPC returns nil payload fields and assert err == nil with nil result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/go/client_test.go` around lines 20 - 218, Add tests that exercise the
"not-found returns (nil, nil)" contract by registering RPC responses with nil
payloads and asserting the client returns (nil, nil): for GetHomeChannel
register rpc.ChannelsV1GetHomeChannelMethod response
rpc.ChannelsV1GetHomeChannelResponse{Channel: nil} then call
Client.GetHomeChannel(...) and assert err == nil and result == nil; for
GetEscrowChannel register rpc.ChannelsV1GetEscrowChannelMethod response
rpc.ChannelsV1GetEscrowChannelResponse{Channel: nil} then call
Client.GetEscrowChannel(...) and assert err == nil and result == nil; for
GetLatestState register rpc.ChannelsV1GetLatestStateMethod response
rpc.ChannelsV1GetLatestStateResponse{State: nil} then call
Client.GetLatestState(...) and assert err == nil and result == nil; and for
GetAppDefinition register rpc.AppSessionsV1GetAppDefinitionMethod response
rpc.AppSessionsV1GetAppDefinitionResponse{Definition: nil} then call
Client.GetAppDefinition(...) and assert err == nil and result == nil; reuse
NewMockDialer, rpc.NewClient(mockDialer) and the same test structure as the
existing happy-path tests.
nitronode/api/channel_v1/get_latest_state_test.go (1)

242-242: ⚡ Quick win

Add a doc comment for the new exported test function.

Please add a short doc comment above TestGetLatestState_NotFound.

Suggested patch
+// TestGetLatestState_NotFound verifies that absent latest state returns success with a nil state payload.
 func TestGetLatestState_NotFound(t *testing.T) {

As per coding guidelines, **/*.go: Follow gofmt formatting and add doc comments on all exported names in Go code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nitronode/api/channel_v1/get_latest_state_test.go` at line 242, Add a short
doc comment above the exported test function TestGetLatestState_NotFound
describing what the test verifies (e.g., that GetLatestState returns a not-found
error/HTTP 404 when no state exists), ensure the comment starts with the
function name ("TestGetLatestState_NotFound ..."), and then run gofmt/goimports
to keep formatting consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nitronode/api/channel_v1/get_latest_state_test.go`:
- Around line 266-267: Replace the non-fatal assertions guarding
ctx.Response.Payload with fail-fast require assertions so the test stops before
any payload dereference: change assert.NotNil(t, ctx.Response.Payload) to
require.NotNil(t, ctx.Response.Payload) and change assert.Nil(t,
ctx.Response.Error()) to require.Nil(t, ctx.Response.Error()), and add/ensure
the testify/require import is present; update the test function (in
get_latest_state_test.go) where ctx.Response is checked to use require.* instead
of assert.*.

---

Outside diff comments:
In `@sdk/ts-compat/src/client.ts`:
- Around line 526-541: The loop in getChannelData swallows all exceptions from
innerClient.getHomeChannel and always throws "Channel ... not found", which
masks transport/auth failures; modify getChannelData to track RPC errors while
iterating assetsBySymbol (e.g., a variable like lastRpcError or a flag
rpcErrorOccurred) and only suppress per-asset errors for transient misses, but
if any getHomeChannel call throws, rethrow a meaningful error (or the first
caught exception) instead of the generic "not found"; keep ensureAssets and the
success path (returning channel and latest state via innerClient.getLatestState)
unchanged, but replace the unconditional throw at the end with logic that throws
the tracked RPC error when present, otherwise throw the original "Channel ...
not found" error.

---

Nitpick comments:
In `@nitronode/api/channel_v1/get_latest_state_test.go`:
- Line 242: Add a short doc comment above the exported test function
TestGetLatestState_NotFound describing what the test verifies (e.g., that
GetLatestState returns a not-found error/HTTP 404 when no state exists), ensure
the comment starts with the function name ("TestGetLatestState_NotFound ..."),
and then run gofmt/goimports to keep formatting consistent.

In `@pkg/rpc/client_test.go`:
- Around line 134-162: Add explicit success tests for nil/omitted nullable
payloads: create additional subtests (or table rows) for
TestClientV1_ChannelsV1GetHomeChannel that register a
ChannelsV1GetHomeChannelResponse where Channel.State is nil and another where
Channel.State is non-nil but Channel.State.Definition is nil (i.e., omit those
fields), using registerSimpleHandlerV1 and the same request via
client.ChannelsV1GetHomeChannel; assert require.NoError(t, err) and
require.Nil(t, resp.Channel.State) or require.Nil(t,
resp.Channel.State.Definition) as appropriate. Apply the same pattern to the
other analogous client tests in this file (the other ranges referenced in the
review) so each test covers non-nil decoding and explicit nil/omitted payload
success cases.

In `@sdk/go/client_test.go`:
- Around line 20-218: Add tests that exercise the "not-found returns (nil, nil)"
contract by registering RPC responses with nil payloads and asserting the client
returns (nil, nil): for GetHomeChannel register
rpc.ChannelsV1GetHomeChannelMethod response
rpc.ChannelsV1GetHomeChannelResponse{Channel: nil} then call
Client.GetHomeChannel(...) and assert err == nil and result == nil; for
GetEscrowChannel register rpc.ChannelsV1GetEscrowChannelMethod response
rpc.ChannelsV1GetEscrowChannelResponse{Channel: nil} then call
Client.GetEscrowChannel(...) and assert err == nil and result == nil; for
GetLatestState register rpc.ChannelsV1GetLatestStateMethod response
rpc.ChannelsV1GetLatestStateResponse{State: nil} then call
Client.GetLatestState(...) and assert err == nil and result == nil; and for
GetAppDefinition register rpc.AppSessionsV1GetAppDefinitionMethod response
rpc.AppSessionsV1GetAppDefinitionResponse{Definition: nil} then call
Client.GetAppDefinition(...) and assert err == nil and result == nil; reuse
NewMockDialer, rpc.NewClient(mockDialer) and the same test structure as the
existing happy-path tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d11ea131-2d5b-4f8c-b770-6f95855a6991

📥 Commits

Reviewing files that changed from the base of the PR and between 80af194 and 7ca8f94.

⛔ Files ignored due to path filters (1)
  • sdk/ts/test/unit/__snapshots__/public-api-drift.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (20)
  • cerebro/commands.go
  • docs/api.yaml
  • nitronode/api/app_session_v1/get_app_definition.go
  • nitronode/api/app_session_v1/get_app_definition_test.go
  • nitronode/api/channel_v1/get_escrow_channel.go
  • nitronode/api/channel_v1/get_escrow_channel_test.go
  • nitronode/api/channel_v1/get_home_channel.go
  • nitronode/api/channel_v1/get_home_channel_test.go
  • nitronode/api/channel_v1/get_latest_state.go
  • nitronode/api/channel_v1/get_latest_state_test.go
  • pkg/rpc/api.go
  • pkg/rpc/client_test.go
  • sdk/go/app_session.go
  • sdk/go/channel.go
  • sdk/go/client_test.go
  • sdk/go/examples/challenge/main.go
  • sdk/ts-compat/src/client.ts
  • sdk/ts/src/client.ts
  • sdk/ts/src/rpc/api.ts
  • sdk/ts/test/unit/client.test.ts

Comment thread nitronode/api/channel_v1/get_latest_state_test.go Outdated
Copy link
Copy Markdown
Collaborator

@ihsraham ihsraham left a comment

Choose a reason for hiding this comment

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

I would keep this open for two closure items.

The backend/API changes and the main Go/TS SDK paths look aligned with the new absence semantics, and the current checks are green. Before this is ready, I would address:

  1. sdk/ts-compat still swallows real getHomeChannel / getLatestState failures in getChannelData and reports them as Channel ... not found (inline).
  2. YNU-915 AC8 asks for test/integration absent-then-present coverage per endpoint, but this diff only adds handler/unit coverage and does not add or update integration tests.

The unresolved assert / require note in get_latest_state_test.go looks non-blocking to me.

Comment thread sdk/ts-compat/src/client.ts Outdated
}
} catch {
// no channel for this asset
// transient RPC failure for this asset — skip
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With the new nullable semantics, getHomeChannel returns null for normal absence, so exceptions from this block are genuine failures. The catch also covers the matched channel’s getLatestState call; if that request fails, getChannelData reports Channel ... not found instead of surfacing the RPC/auth/transport error.

Please rethrow the lookup error, or only throw not-found after all lookups completed successfully and returned no match.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, good catch. Rewrote getChannelData in 50f812e to track lookup errors per asset and rethrow if every getHomeChannel call failed — only per-asset misses are suppressed now. getLatestState on the matched channel also runs outside the catch so its failures surface as well.

Copy link
Copy Markdown
Contributor

@nksazonov nksazonov left a comment

Choose a reason for hiding this comment

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

Good work — clean approach, and the core change (nil instead of error for absent resources) is implemented consistently across all four endpoints. A few things need attention before landing.


Out-of-diff notes:

sdk/ts/examples/example-app/src/components/WalletDashboard.tsx:209 — Inside the allowance-error recovery path for handleCheckpoint, state.homeLedger.blockchainId is accessed without a null check. getLatestState now returns null for absent state; if this path is hit before a channel exists, it throws TypeError: Cannot read properties of null. Fix: guard with if (!state) throw new Error('no state available for approval'); before the blockchainId access.

sdk/ts/examples/example-app/src/components/ActionModal.tsx:123 — Same issue: latestState.homeLedger.blockchainId immediately after getLatestState(...) with no null check. Same fix pattern.

sdk/ts-compat/src/rpc.ts:434parseGetAppDefinitionResponse is a public exported compat function. When the server omits definition (new nil-response case), it returns { params: {} }. Callers who access params.protocol, params.quorum, or params.participants get undefined silently — no error, no null signal. Fix: return { params: null } and document that callers must check before accessing fields.

sdk/ts-compat/test/unit/ — The nil-to-throw bridges added in NitroliteClient.getAppDefinition and NitroliteClient.getEscrowChannel (both throw when inner SDK returns null) have no tests. Add a test for each that mocks innerClient to return null and verifies the expected throw.

Comment thread sdk/ts-compat/src/client.ts Outdated
Comment thread sdk/go/client_test.go
Comment thread sdk/ts/test/unit/client.test.ts
Comment thread sdk/ts/test/unit/client.test.ts
- sdk/ts-compat: fix getChannelData to surface lookup errors instead of
  reporting "not found", and reject matched channels with null latest
  state (would have leaked state=null to v0.5.3 callers).
- nitronode/get_latest_state_test: use require before payload deref in
  the NotFound test; add doc comment.
- pkg/rpc/client_test, sdk/go/client_test, sdk/ts/test/unit/client.test:
  add absence-semantics coverage for getHomeChannel, getEscrowChannel,
  getLatestState, getAppDefinition, and the nil-state guards in
  checkpoint, closeHomeChannel, and submitAppSessionDeposit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@philanton
Copy link
Copy Markdown
Contributor Author

@ihsraham both closure items addressed in 50f812e:

  1. sdk/ts-compat/getChannelData rewritten — per-asset getHomeChannel errors are tracked but no longer all swallowed; if every lookup blew up, the last error is rethrown instead of the bogus "not found". getLatestState on the matched channel runs outside the catch so its failures surface too. Also covers @nksazonov's catch on the state: null leak — matched channel with absent state now throws explicitly instead of returning {channel, state: null} to v0.5.3 callers.
  2. AC8 — the ticket was edited to drop the test/integration coverage, since we don't have that setup wired up yet(. Handler/unit coverage stays.

Also picked up @nksazonov's test-coverage notes — added Client-level null-return tests for getHomeChannel/getEscrowChannel/getAppDefinition and nil-state guard tests for checkpoint/closeHomeChannel/submitAppSessionDeposit, plus matching _NilResponse / _NilChannel cases on the Go side.

Copy link
Copy Markdown
Collaborator

@ihsraham ihsraham left a comment

Choose a reason for hiding this comment

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

Re-reviewed the latest head 50f812eae05378ee62957cd26c1017df8efc337a and the author replies.

The added Go/RPC/TS unit coverage and the fail-fast assertion change look good. I am not keeping the test/integration item as a blocker if YNU-915 was updated as described in the author reply.

One compat issue remains: the getChannelData fix still converts partial lookup failures into Channel ... not found.

Comment thread sdk/ts-compat/src/client.ts Outdated
}
return { channel: ch, state };
}
if (!sawSuccessfulLookup && lookupError) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This still masks partial lookup failures. If one asset lookup throws and any other asset lookup succeeds without matching, sawSuccessfulLookup is true and this falls through to Channel ... not found even though the requested channel may be behind the failed lookup.

With the nullable contract, misses come back as null; a caught exception means that asset was not inspected. Please rethrow when lookupError is set before emitting not-found, unless a matching channel was already found.

Copy link
Copy Markdown
Contributor Author

@philanton philanton May 19, 2026

Choose a reason for hiding this comment

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

Yep, you're right — the sawSuccessfulLookup gate let partial failures slip through to "not found". Dropped it in ea3dcb5. Now any unresolved lookupError rethrows before the not-found path, since a caught exception means that asset wasn't inspected and we can't conclude absence.

Per @ihsraham: a caught exception means that asset was not inspected,
so a fall-through "not found" misreports state when the requested
channel may have been behind a failed lookup. Drop the
sawSuccessfulLookup gate and rethrow whenever lookupError is set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@ihsraham ihsraham left a comment

Choose a reason for hiding this comment

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

Approving from my side. The latest getChannelData update now rethrows any captured lookup error before the not-found path, so the partial-failure case is covered.

I also rechecked the author replies and the added nil-response coverage from the prior commit. No remaining blocker from my previous review.

# Conflicts:
#	docs/api.yaml
#	sdk/ts/src/client.ts
Copy link
Copy Markdown
Contributor

@nksazonov nksazonov left a comment

Choose a reason for hiding this comment

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

Great job!

@philanton philanton merged commit b983b23 into main May 19, 2026
16 checks passed
@philanton philanton deleted the fix/get-endpoint-errors branch May 19, 2026 14:02
@philanton philanton mentioned this pull request May 28, 2026
3 tasks
philanton added a commit that referenced this pull request May 28, 2026
## Summary

Patch release that re-publishes \`@yellow-org/sdk-compat\` with a
correct build. The \`@yellow-org/sdk-compat@1.3.0\` tarball shipped a
stale \`dist/\` (built May 5), so it is missing runtime changes from
#699 (ts-compat runtime migration surface), #748 (absent-resource GET
returns), and #758 (audit final session-key changes).
\`@yellow-org/sdk@1.3.0\` rebuilt correctly via its existing
\`prepublishOnly\` hook.

## Changes

- Bump \`@yellow-org/sdk\`, \`@yellow-org/sdk-compat\`,
\`@yellow-org/sdk-mcp\` → \`1.3.1\` (strict-mirror policy)
- Add \`prepublishOnly: clean + build:prod\` to
\`@yellow-org/sdk-compat\`
- Tighten \`@yellow-org/sdk-mcp\` \`prepublishOnly\` to clean \`dist/\`
and \`content/\` before build

## Post-merge

- [ ] Deprecate \`@yellow-org/sdk-compat@1.3.0\` on npm
- [ ] Tag \`v1.3.1\` on stable HEAD
- [ ] Local publish \`@yellow-org/sdk@1.3.1\` +
\`@yellow-org/sdk-compat@1.3.1\` + \`@yellow-org/sdk-mcp@1.3.1\`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

3 participants