Skip to content

chore: bump SDK to 0.2.78 with parity updates#25

Merged
arcaputo3 merged 2 commits intomainfrom
chore/bump-sdk-0.2.78
Mar 20, 2026
Merged

chore: bump SDK to 0.2.78 with parity updates#25
arcaputo3 merged 2 commits intomainfrom
chore/bump-sdk-0.2.78

Conversation

@arcaputo3
Copy link
Copy Markdown
Contributor

Summary

  • Bump @anthropic-ai/claude-agent-sdk from 0.2.77 to 0.2.78
  • Add StopFailure hook event — fires when the agent fails to stop cleanly (e.g., API error during stop)
  • Add BridgeMetadata system message — carries slash_commands metadata

Test plan

  • ./mill agent.compile passes
  • ./mill agent.test passes (new tests for StopFailure parsing and bridge_metadata parsing)
  • ./mill examples.compile passes

🤖 Generated with Claude Code

Add StopFailure hook event (fires when agent fails to stop cleanly)
and BridgeMetadata system message (carries slash_commands). Includes
parsing, tests, and package.json version pin.

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

claude bot commented Mar 18, 2026

PR Review: chore: bump SDK to 0.2.78 with parity updates

Overall this is a clean, focused PR. The new StopFailure hook and BridgeMetadata message follow the existing patterns well. A few things worth addressing:


Issues

1. bridge_metadata missing guardTopLevelUnknown wrapper (minor bug risk)

In MessageConverter.scala, every other subtype in parseSystemEnvelope is wrapped with guardTopLevelUnknown for forward-compatibility protection — but bridge_metadata is not:

case Some("api_retry") =>
  guardTopLevelUnknown(raw, "system", Some("api_retry"), context) {  //
    parseApiRetryMessage(obj, raw)
  }
case Some("bridge_metadata") =>
  AgentMessage.BridgeMetadata(  // ❌ no guard
    slashCommands = stringArrayField(obj, "slash_commands"),
    ...
  )

Unless guardTopLevelUnknown is specifically meant only for fallible parsers, this inconsistency should be resolved.


2. BridgeMetadata.slashCommands will serialize to wrong field name (potential JSON round-trip bug)

AgentMessage has a derived JsonEncoder/JsonDecoder at line 182–183. With zio-json's derived codecs, slashCommands (camelCase) will be encoded as "slashCommands", not "slash_commands". If BridgeMetadata ever flows through the JSON codec path (e.g., in transcript serialization), it will be inconsistent with the SDK wire format.

Consider adding a @jsonField("slash_commands") annotation:

case BridgeMetadata(
    @jsonField("slash_commands") slashCommands: List[String],
    uuid: MessageUuid,
    sessionId: SessionId
)

3. Version pin in package.json (minor inconsistency)

// before
"@anthropic-ai/claude-agent-sdk": "^0.2.77"
// after
"@anthropic-ai/claude-agent-sdk": "0.2.78"

The caret was dropped, pinning to an exact version. This is inconsistent with the other dependencies (@a2a-js/sdk: "^0.3.12", zod: "^4.0.0"). The lockfile already pins the resolved version, so this change in package.json only matters for future bun install runs. If intentional (to avoid unexpected bumps), a brief comment or PR note would clarify the intent.


Minor Observations

4. error field in StopFailure lacks camelCase fallback

HookCallback.scala:240:

error = AssistantMessageError.fromString(firstString(raw, "error").getOrElse("unknown")),

Other fields on the same struct (e.g., errorDetails, lastAssistantMessage) support both snake_case and camelCase variants via multiple args to firstString. If the SDK can emit either casing here, "error" is missing the camelCase counterpart. If it's always "error", this is fine as-is.

5. Missing edge-case tests

  • StopFailure with no error field: should fall back to AssistantMessageError.Unknown — this is the most likely failure-mode edge case and worth a test.
  • BridgeMetadata with absent slash_commands: should return an empty List — confirms stringArrayField default behavior.

What's Good

  • The StopFailure data model is well-designed — making error a required non-optional field (unlike Assistant.error) correctly reflects that a failure hook always has an error.
  • All three locations in HookEvent (toDisplayString, fromString, and unsafe fromString) are updated consistently — easy to miss one.
  • Tests cover the happy path for both new message types and are easy to read.

🤖 Generated with Claude Code

- Wrap bridge_metadata parsing with guardTopLevelUnknown for consistency
- Add @jsonField("slash_commands") annotation for JSON round-trip correctness
- Restore caret in package.json version specifier
- Add edge-case tests: StopFailure with missing error, empty slash_commands

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

claude bot commented Mar 19, 2026

Code Review

Overall this is a clean, focused PR that follows the established patterns well. A few observations:

Strengths

  • Consistent three-location update in HookEvent.scalatoName, fromString (safe), and unsafeFromString are all updated for StopFailure. Easy to miss one of these.
  • Graceful fallback for error fieldfirstString(raw, "error").getOrElse("unknown") correctly maps to AssistantMessageError.Unknown when the field is absent, maintaining forward compatibility.
  • stringArrayField is null-safe — it delegates to optionStringArray which returns Nil on missing/non-array fields, so BridgeMetadata will never throw on a malformed slash_commands field.
  • Test coverage is solid — both the present-fields and absent-fields paths are exercised for StopFailure, and both empty and non-empty arrays are tested for BridgeMetadata.

Minor Observations

1. Missing test: bridge_metadata with absent slash_commands

There's no test verifying behaviour when slash_commands is entirely absent from the payload. stringArrayField will silently return Nil, which is the right behaviour, but an explicit test would document that contract:

test("parses bridge_metadata with missing slash_commands"):
  val raw = js.Dynamic.literal(
    `type` = "system",
    subtype = "bridge_metadata",
    uuid = "msg-bridge-3",
    session_id = "session-1"
  )
  MessageConverter.fromRaw(raw) match
    case AgentMessage.BridgeMetadata(slashCommands, _, _) =>
      assertEquals(slashCommands, Nil)
    case other =>
      fail(s"Expected BridgeMetadata, got: $other")

2. BridgeMetadata and extension methods

AgentMessage has extension methods (text, toolCalls, isAssistant, etc.) with exhaustive-ish match arms. BridgeMetadata falls through to the wildcard catch-all in each, which is correct — but it's worth confirming that no downstream match expressions in consumer code will raise a warning about non-exhaustive patterns once this case is added. Since AgentMessage is a Scala enum (sealed), any exhaustive match elsewhere in the codebase that doesn't include BridgeMetadata will produce a compiler warning. A quick ./mill agent.compile confirming zero new warnings would close that loop (the PR description says it passes, so this is just a note for reviewers).

3. @jsonField("slash_commands") on BridgeMetadata

The annotation is correct for the ZIO JSON codec, but the actual streaming parse path in MessageConverter bypasses JSON derivation entirely (using stringArrayField on js.Dynamic). Both paths are consistent, so this isn't a bug — just worth knowing that changes to the field name would need to be made in two places.

Summary

No blocking issues. The two main additions (StopFailure hook and BridgeMetadata message) are implemented cleanly and symmetrically with existing patterns. Suggest adding the missing slash_commands-absent test case before merge; everything else is informational.

Copy link
Copy Markdown

@cbelltjc cbelltjc left a comment

Choose a reason for hiding this comment

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

LGTM :shipit:

@arcaputo3 arcaputo3 merged commit 4d56d08 into main Mar 20, 2026
4 checks passed
@arcaputo3 arcaputo3 deleted the chore/bump-sdk-0.2.78 branch March 20, 2026 19:36
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.

2 participants