Summary
The /goal command emits goal_status attachments — in the live stream and persisted in the session transcript — but they are not represented in the SDK's typed SDKMessage union or as a typed attachment. Consumers receive a loosely-typed Record<string, unknown> and must reverse-engineer the goal lifecycle themselves.
Environment
@anthropic-ai/claude-agent-sdk: observed on 0.3.148 → 0.3.154 (latest at time of writing)
- Consumer: a desktop Claude app rendering a goal-progress UI
Detail
A single /goal run produces a sequence of goal_status attachments that we classify into three shapes by inspecting raw fields:
sentinel: true → goal set (no evaluation yet; just the condition)
met: true → goal complete (carries reason, and observed iterations / durationMs / tokens)
- otherwise,
reason present → per-turn check (not yet met)
None of this is typed or documented. We reverse-engineered the discriminant across 0.3.148 / 0.3.150 / 0.3.154 and pinned it with fixtures — which is brittle across SDK updates (a field appearing/disappearing silently changes classification).
Request
Expose goal_status as a typed attachment/message variant — ideally a discriminated union (e.g. on kind: 'set' | 'check' | 'complete') so each variant carries only its valid fields — and include it in the exported types. Even a documented non-union interface + a note in the README would remove the guesswork.
Why this matters
Any consumer that surfaces /goal progress hits this and ends up re-deriving the same lifecycle classification from untyped fields. A typed surface (like the existing typed SDKMessage variants) would make this robust across SDK versions.
Summary
The
/goalcommand emitsgoal_statusattachments — in the live stream and persisted in the session transcript — but they are not represented in the SDK's typedSDKMessageunion or as a typed attachment. Consumers receive a loosely-typedRecord<string, unknown>and must reverse-engineer the goal lifecycle themselves.Environment
@anthropic-ai/claude-agent-sdk: observed on 0.3.148 → 0.3.154 (latest at time of writing)Detail
A single
/goalrun produces a sequence ofgoal_statusattachments that we classify into three shapes by inspecting raw fields:sentinel: true→ goal set (no evaluation yet; just the condition)met: true→ goal complete (carriesreason, and observediterations/durationMs/tokens)reasonpresent → per-turn check (not yet met)None of this is typed or documented. We reverse-engineered the discriminant across 0.3.148 / 0.3.150 / 0.3.154 and pinned it with fixtures — which is brittle across SDK updates (a field appearing/disappearing silently changes classification).
Request
Expose
goal_statusas a typed attachment/message variant — ideally a discriminated union (e.g. onkind: 'set' | 'check' | 'complete') so each variant carries only its valid fields — and include it in the exported types. Even a documented non-union interface + a note in the README would remove the guesswork.Why this matters
Any consumer that surfaces
/goalprogress hits this and ends up re-deriving the same lifecycle classification from untyped fields. A typed surface (like the existing typedSDKMessagevariants) would make this robust across SDK versions.