Skip to content

feat: generate status-dependent action attempt properties as nullable - #322

Merged
razor-x merged 9 commits into
betafrom
claude/action-attempt-status-annotations-f216kn
Aug 27, 2026
Merged

feat: generate status-dependent action attempt properties as nullable#322
razor-x merged 9 commits into
betafrom
claude/action-attempt-status-annotations-f216kn

Conversation

@razor-x

@razor-x razor-x commented Aug 27, 2026

Copy link
Copy Markdown
Member

What this means for SDK users

Every action attempt now deserializes to a subclass for its exact (action_type, status) pair, so Error and Result exist only where the API actually populates them:

var attempt = await seam.Locks.LockDoorAsync(new() { DeviceId = deviceId });

// attempt.Result — does not compile: Result is not on the base type.

switch (attempt)
{
    case ActionAttemptLockDoorSuccess success:
        var confirmed = success.Result.WasConfirmedByDevice; // Result is non-null here
        break;
    case ActionAttemptLockDoorError error:
        var message = error.Error.Message; // Error is non-null here
        break;
    case ActionAttemptLockDoorPending:
        // no Error or Result members at all
        break;
}

Unknown discriminator values still fall back safely with the raw payload preserved: an unknown action_type gives ActionAttemptUnrecognized, an unknown status gives e.g. ActionAttemptLockDoorUnrecognized. attempt.Status and attempt.ActionAttemptId stay on the base for polymorphic reads, and the waiter (waitForActionAttempt) behaves exactly as before. A failed attempt without an error object raises SeamActionAttemptFailedException with the message "Action attempt failed" instead of assuming one.

Runtime API behavior is unchanged — the API always sent null; the types just stopped lying about it. Code that read attempt.Error/attempt.Result off the base type must now pattern-match on the status subclass.

How

Bumps @seamapi/blueprint from ^1.8.0 to ^1.10.0, which annotates each action-attempt property with the statuses that populate it. The generator nests a second SeamUnion level under each action type — the existing converter composes for nested unions with no runtime changes. Property placement per status comes from the annotation alone; the status/error wire types remain the runtime-owned ActionAttemptStatus/ActionAttemptError the resolver depends on. @seamapi/types stays at 1.1034.0 (the annotations are complete against it; bumping would import unrelated API churn). The blueprint 1.9.x ordering change reorders request parameters in 7 Routes files — mechanical churn from the bump.

Generated sources are produced by this repo's Generate workflow on push (it runs npm run generate + csharpier and auto-commits), so this PR carries the codegen change, dependency bumps, runtime exception change, and tests.

Verification

Built and tested locally on net8.0: 175/175 tests pass, including new tests for pending/success/error subclass deserialization, unknown-status fallback with raw JSON, and the exception fallback without an error object. The wait-for-action-attempt integration tests exercise the failure path end to end. Codegen tsc + eslint + prettier clean; regeneration is deterministic. Not run locally: the net10.0 target and csharpier (CI covers both).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp

claude and others added 9 commits August 27, 2026 01:05
Bump @seamapi/blueprint to ^1.10.0 and consume its per-property
actionAttemptStatuses annotation. An action attempt property that the
API only populates for some statuses now generates as a nullable C#
type, with documentation naming the statuses that populate it. In
practice every variant's result becomes nullable, documented as null
unless the action attempt status is success; error was already declared
nullable on the union base. The generated sources are regenerated and
formatted by the Generate workflow on push.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp
…otations-f216kn' into claude/action-attempt-status-annotations-f216kn
Each action type is now a nested union discriminated on status: a
pending subclass with no error or result members, a success subclass
with a non-null result, an error subclass with a non-null error, and an
unrecognized fallback preserving the raw payload. Property placement
comes from the actionAttemptStatuses annotation, so dereferencing a
status-dependent property without narrowing to its status subclass does
not compile. SeamActionAttemptFailedException reads the error from the
status subclass that carries it and falls back when none does. The
generated sources are regenerated and formatted by the Generate
workflow on push.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp
…otations-f216kn' into claude/action-attempt-status-annotations-f216kn
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp
@razor-x
razor-x merged commit f38e8d2 into beta Aug 27, 2026
11 checks passed
@razor-x
razor-x deleted the claude/action-attempt-status-annotations-f216kn branch August 27, 2026 20:29
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