Skip to content

Latest commit

 

History

History
94 lines (72 loc) · 28 KB

design-unknown-records.md

File metadata and controls

94 lines (72 loc) · 28 KB

Unknown harness records — census + integration + schema-drift design

Read-only census of this instance (2026-09-20). Sources: telemetry shards 07/08/09 (2,138 unknown-record events, one per process per name — so counts below are processes that saw it, capped at 200 by the shard window), 35 live buffers (32 claude / 3 codex), 38 readable recent transcripts (2 skipped >512 MB), 20 newest codex rollouts (15 × 0.153.4, 5 × 0.154.0), the installed claude 2.1.274 binary (zod SDK schemas with describe() text — 153 record shapes, 51 system subtypes) and the codex 0.154.0 native binary (serde struct/variant strings).

1. Inventory

Kind: card = renders the red Unknown-event card today · decl-ign = on a KNOWN_IGNORED list (telemetry still fires, no card) · handled = routed since the version noted.

Name Kind today Processes (telemetry) / records (corpus) First → last seen Sample (redacted) Proposal Owning file
system/notification card 200 / 43 in live buffers 2.227.5 → 2.369.120 {key:"stop-hook-error", text:"Stop hook error occurred · ctrl+o to see", priority:"immediate"} — binary: the REPL notification queue (`key,text,priority low medium high
system/api_error card (history only — never on stdout, the stream twin is api_retry) 200 / 27 rows in 38 transcripts; status 529×75, 429×36, 503×6, 401×6, 502×2, 500×2; source:"request_retry" ×90 2.227.5 → 2.369.120 {error:{message:"529 …overloaded_error…", status:529, formatted:"529 Overloaded", isNetworkDown:false, rateLimits:null}, retryInMs:578, retryAttempt:1, maxRetries:10} Declare-ignored on the transcript path (its live twin already drives the "API retrying (n/10)" spinner label). DELIVERED (r3 2026-09-21): the transcript rows are NOT consumed for side effects — a days-old 401 replayed at resume would evict TODAY's member (the mark keys on the session's current slot) and the live api_retry twin fires on the first request of any resume; the 401/403 → notePoolAuthFailure consumer exists on the LIVE feed as forward-compat for the stream record the binary declares (never observed on stdout in the census) src/message-manager.js (KNOWN_IGNORED + reason), src/server/session-brain.js noteApiErrorAuth (forward-compat), src/server/stdout/claude-stream-json.js
system/turn_duration card (history only) 185 / 12 rows 2.227.5 → 2.369.118 {durationMs:198441, messageCount:66}; binary adds budget_tokens, budget_limit, budget_nudges, pending_background_agent_count, pending_workflow_count ("Done in Ns / Waiting for N agents") Attach to the preceding result message's meta (message-meta popup row "Turn: 3m18s · 66 messages · budget n/limit"); card-less src/message-manager.js _processResult meta merge
system/vcs_state_changed card 173 / 3 (push×3, commit×1) 2.227.8 → 2.369.120 {kind:"push", branch:"fix/…", cwd:"…"}; binary: `kind commit push merge
system/code_change_published card 121 / 1 2.227.5 → 2.369.120 {provider:"github", url:"https://github.com/<org>/<repo>/pull/608", repo:"<org>/<repo>", identifier:"608", action:"pushed"}; binary enum `action created edited merged
system/local_command card (history only; not in the SDK union — internal REPL row) 46 / older transcripts 2.359.0 → 2.369.120 {content:"<command-name>/branch</command-name>…", level:"info"} — the TUI user's slash command as persisted Render through the existing <command-name> user-command bubble (chat-renderers already strips these tags); card-less src/message-manager.js _processSystem → reuse command renderer
system/away_summary card (history only) 31 2.322.0 → 2.355.1 {content:"Designed the … canvas …; it's published and verified. Next: … (disable recaps in /config)"} — binary: "summary of what happened while the user was away" Dim system "Recap" notice card (content is model text → markdown through DOMPurify) src/message-manager.js, src/lib/chat-renderers.js
system/microcompact_boundary card if ever seen again 20 2.283.0 → 2.322.0 not in 2.1.274's SDK union; the REPL renders null for it Declare-ignored with reason "legacy 2.1.2xx micro-compaction marker, REPL renders nothing" src/message-manager.js KNOWN_IGNORED
system/thinking_tokens decl-ign 200 / 1188 — {estimated_tokens:50, estimated_tokens_delta:50} Keep ignored; optional: feed the composer spinner ("thinking ~2.1k tokens") —
system/background_tasks_changed decl-ign 173 / 38 — {tasks:[{task_id, task_type:"local_workflow", description}]} — a LEVEL signal (full live set) Status-bar chip "N background tasks" + reconcile the Agents tree (a BACKGROUNDED task missing from the set is SOFT-closed — finished, outcome not reported — even if its notification was lost; r3: the set never names a foreground call (task_started fires for those too), and the CLI drops membership ~2 records BEFORE the outcome record, so the close is a guess the outcome overwrites) src/message-manager.js (the ONE reconcile owner), src/lib/chat-status-bar.js (chip + tail only)
system/task_updated decl-ign 172 / 25 — {task_id, patch:{status:"failed", end_time}} Apply the patch to the task card (closes a failed task without waiting for task_notification; overrides a level-set soft close) src/message-manager.js task branch
system/hook_started / compact_boundary decl-ign (server consumer uses both) 195 / 200 — — none —
system/status, api_retry, command_lifecycle, rate_limit_event, control_cancel_request handled (2.369.67 / — / 2.369.111 / 2.289.0 / 2.369.49) 67 / 5 / 63 / 48 / 2 — — none (telemetry stopped at those versions — the lists lagging the handler class) —
codex item_completed:Extension:clock.sleep handled (routed after 2.369.53) 5 / 23 rollout records 2.369.46 → .53 {item:{type:"Extension", kind:"clock.sleep", durationMs:30000}} — 0.154 prompt text: "use clock.sleep to wait for external events" (follow-up/daybreak) none —
codex item_completed:McpToolCall card (3 per open of that thread; NOT routed, no function_call / mcp_tool_call_* twin in the rollout — verified) 0 (thread never opened here) / 3 2026-09-13 {item:{type:"McpToolCall", server:"<mcp-server>", tool:"execute_…", arguments:{code:…}, status:"completed", result:{content:[…]}, duration}}; binary: ThreadItem::McpToolCall 12 fields (+connectorId, mcpAppResourceUri, linkId, appName, actionName, readOnlyHint) Route to the MCP tool card (same card as the mcp function_call pair; fold kind mcp) src/codex-message-manager.js _processItemCompleted
codex record _stdin_ack card (1) 1 2.369.75 wrapper's own stdin ack leaked into the codex reader (claude's list already ignores it) Add to SKIPPED_RECORD_TYPES "wrapper ack, consumed by the server" src/codex-message-manager.js
codex thread_settings_applied, turn_aborted, task_failed, world_state, inter_agent_communication_metadata handled / skipped 141 / 2 / 6 / 182 / 2862 — task_failed {error:"You've hit your usage limit…", codexErrorInfo:"usageLimitExceeded", resetsAt:null} none —
ws-unknown-type — 0 — — none —
workflow journal rows launched, started{key,agentId,label,phase}, result{key,agentId,result} never reach a normalizer (only /api/workflow reads subagents/workflows/*/journal.jsonl) 7 / 33 / 29 — — none — but the census tool must exclude that path by rule (they look like claude records) scripts/test-record-shape.mjs

Declared upstream, never seen here (2.1.274 union minus HANDLED ∪ KNOWN_IGNORED): post_turn_summary, task_summary, permission_retry, memory_saved, agents_killed, thinking, mirror_error, control_request_progress, model_fallback, model_consent_fallback, file_snapshot, scheduled_task_fire, peer_message_hold, local_command_output, hook_progress, plugin_install, files_persisted, feedback_draft_queued, worker_shutting_down, turn_handoff_available, memory_recall, elicitation_complete, permission_denied, dev_intent, turn_preempted, cloud_session_delta, upgrade_relay_marker; top-level tool_use_summary, prompt_suggestion, conversation_reset, api_metrics, os_notification, apply_flag_settings, set_expanded_view, active_goal, autocompact_state, hint_clears, open_message_selector, transcript_mirror, auth_status. Several are money/attention-relevant before they ever appear: model_consent_fallback ("the Fable 5 usage-credit gate swapped the model"), permission_denied (auto-denied tool), peer_message_hold (a vibespace-msg held by receive-side policy), active_goal, turn_preempted, conversation_reset, scheduled_task_fire, os_notification.

Seen 2026-10-03 (lane classifier-stop-card, 2.369.205): informational left the list above — CLI 2.1.287 / 2.1.288 write it on both carriers (a safety-classifier stop's notice, measured in this box's lane transcripts). It is HANDLED: a dim card with the CLI's words at level; the safety stop's notice + its model-only nudge (isMeta, turnCompanion) are ONE worded stop card read from a census (src/safety-stop.js). Gate: scripts/test-unknown-records.mjs §10.

2. Top 5 worth doing now

  1. The schema oracle + drift module (§3) — one PURE file + one fast-tier suite that reads the installed binary's union: every new CLI version fails the build with the subtype/field named, before a user ever sees a red card. ~1 day incl. tests.
  2. system/notification → real notice — 43 red cards in today's live buffers are the CLI's own user-facing queue; priority routing + key dedupe + server toast for immediate. ~half day.
  3. Codex McpToolCall routing + _stdin_ack skip — every MCP-using codex thread shows red cards per call; fixture = the real sample. ~2 h.
  4. History-only rows in one PR: local_command → command bubble, away_summary → recap card, turn_duration → result meta, api_error → declared-ignored + 401/403 auth side effect, microcompact_boundary → declared-ignored. ~half day; removes every red card in old transcripts.
  5. VCS/PR/background-task signals to chrome: vcs_state_changed → git chip + explorer refresh, code_change_published → PR chip (unify with pr-link), background_tasks_changed → status-bar count. ~1 day.

3. Schema-drift design (known types with unknown fields)

Finding that motivates it: the live buffers already carry three fields on handled shapes that the 2.1.274 SDK schema does not declare — system/init.messaging_socket_path, system/task_started.owned_by_subagent, system/task_progress.workflow_progress (all three exist as strings in the binary, none is in the zod object; the SDK objects are plain z.object, so the CLI emits more than it publishes). VibeSpace silently depends on one of them (workflow_progress). Nothing today would notice a fourth.

Module: src/record-shape.js — PURE, imports nothing, CJS so the bundle carries it.

  • SHAPES[key] = { known:Set, ignored:Map<field, reason>, enums:{field:Set} }, key = <harness>:<carrier>:<shape> — e.g. claude:stream:system/init, claude:transcript:system/api_error, codex:rollout:item/McpToolCall. Carriers are separate because the wire is snake_case (duration_ms) and the transcript row is camelCase (durationMs) with an envelope — a transcript row is never judged by the stream schema.
  • ENVELOPES: claude transcript {parentUuid, logicalParentUuid, isSidechain, isMeta, timestamp, level, userType, entrypoint, cwd, sessionId, version, gitBranch, slug, promptId, requestId, uuid, __line}; codex rollout {timestamp, ordinal, type, payload}; VibeSpace-own wrapper fields {webui_peer, webui_queue_id, webui_after_commit, webui_origin, webui_origin_note, modelPinned, wrapper, permissionMode, session_name, agent_role, item_id, thread_id, turn_id} — a declared group "ours", never drift.
  • shapeKeyOf(harness, carrier, record), unknownFields(harness, carrier, record) → {shape, fields:[…], enumDrift:[{field,value}]} | null. Depth: top level plus the declared nested objects only (rate_limit_info, unifiedWindows, compact_metadata, usage, error, item, info, rate_limits); user/assistant are judged by content-block type only (text, thinking, redacted_thinking, tool_use, tool_result, image, document, search_result, fallback).
  • A field is never deleted from known, only moved to ignored with a reason (old transcripts replay).

Hook: in MessageManager._processMessage and CodexMessageManager._processRecord/_processEvent/_processItemCompleted, after routing: this._noteShapeDrift(record). Per-session Set of shape|field ⇒ ONE card per shape per session listing the new fields; a second occurrence adds fields to that card (meta op) instead of a new card. Emits {noticeKind:'unknown-fields', content:[{type:'unknown_fields', harness, shape, fields, enumDrift, sample}]}; sample = unknownRecordJson(record) through a redactor that masks values under keys matching /token|secret|key|authorization|password/i. Telemetry harness-shape-drift once per process per shape (detail = shape +{a,b}), same as the record breadcrumb.

User surface: a card titled "New fields on a known record: system/init +{messaging_socket_path}" — red border like the Unknown-event card but with the dim variant of the head; "Full record" expander; folds under the existing unknown kind of chat.collapseKinds (label becomes "N unknown events / new fields"). Diagnostics: the telemetry byName list already surfaces the names; add a "Harness drift" block that lists harness-shape-drift + cli-unknown-* + codex-unknown-record:* with first/last seen and CLI version (the buffer's claude_code_version / rollout cli_version).

False positives: (a) ignored per shape is a DECISION with a reason, exactly like the record lists; (b) envelope and "ours" groups; (c) carrier split; (d) depth limit; (e) enum drift is informational — the binary itself says "treat an unrecognized kind exactly like a recognized one", so handlers must keep working and only the card fires; (f) fields historically emitted stay known; (g) the workflow journal path and fixture-guard synthetic ids are excluded by rule.

Tests — scripts/test-record-shape.mjs (fast tier, registered in ci.mjs): §1 census leg over the tracked fixtures (scripts/fixtures/**, never production ~/.claude — the 2026-09-09 fixture rule): every field on every handled shape ∈ known ∪ ignored ∪ envelope ∪ ours; an optional printed local leg over ~/.claude/projects + ~/.codex/sessions behind VIBESPACE_SHAPE_CENSUS=1 (SKIP with reason in the gate). §2 synthetic: system/init +{foo,bar} → one card naming both, the second record adds no card, telemetry once; codex token_count.info +{baz} likewise. §3 enum leg: vcs_state_changed kind:"squash" → enum-drift card, handler still runs. §4 binary oracle (SKIP with reason when no claude binary): the extractor used for this census (walk u({type:R("…"),subtype:R("…")…}) skipping string literals) reads the installed binary's union and asserts every subtype ∈ HANDLED ∪ KNOWN_IGNORED ∪ DECLARED_UPSTREAM_UNSEEN (the list above, each with a one-line disposition), and every shape's binary field set ⊆ known ∪ ignored — red locally on the day the CLI updates, printed in the mirror. §5 negative control: a scratch copy with ignored emptied must go red on §1.

KNOWN_FIELDS dumped (claude 2.1.274 stream; uuid, session_id on every shape, omitted):

  • system/init: agents, apiKeySource, startup_timing, betas, claude_code_version, cwd, tools, mcp_servers, model, permissionMode, slash_commands, terminal_slash_commands, output_style, skills, plugins, plugin_errors, plugin_warnings, mcp_server_errors, fast_mode_state, fast_mode_disabled_reason, footer_indicator, effort, capabilities, analytics_disabled, product_feedback_disabled, memory_paths, worker_epoch, powershell_path, cloud_session (+ corpus, undeclared: messaging_socket_path)
  • system/task_started: task_id, tool_use_id, description, subagent_type, is_backgrounded, spawn_depth, task_type, workflow_name, prompt, skip_transcript, ambient (+ undeclared: owned_by_subagent) · task_progress: task_id, tool_use_id, description, subagent_type, usage, last_tool_name, summary (+ undeclared: workflow_progress) · task_notification: task_id, tool_use_id, status{completed,failed,stopped}, reason{worker_restart}, output_file, summary, usage{total_tokens,tool_uses,duration_ms}, resource_links, skip_transcript, ambient · task_updated: task_id, patch · background_tasks_changed: tasks
  • result (success): duration_ms, duration_api_ms, ttft_ms, ttft_stream_ms, time_to_request_ms, user_message_uuid, user_message_uuids, resume_reason, local_command, request_sent_wall_ms, first_content_frame_ms, first_stream_post_ms, first_stream_post_ack_ms, first_stream_post_wall_ms, time_to_request_from_spawn_ms, warm_spare_claimed, time_origin_ms, is_error, api_error_status, api_error_code, num_turns, result, stop_reason, total_cost_usd, usage, modelUsage, subagent_stats, permission_denials, queued_turn_count, structured_output, deferred_tool_use, terminal_reason, result_index, fast_mode_state, fast_mode_disabled_reason, origin; (error): + errors, runner_exit, startup_failure_reason
  • rate_limit_event: rate_limit_info{status{allowed,allowed_warning,rejected}, resetsAt, rateLimitType{five_hour,seven_day,seven_day_opus,seven_day_sonnet,seven_day_overage_included,overage}, utilization, unifiedWindows{five_hour,seven_day,seven_day_overage_included}{utilization,resetsAt}}
  • tool_progress: tool_use_id, tool_name, parent_tool_use_id, elapsed_time_seconds, task_id, heartbeat, subagent_type, subagent_retry{agent_id,attempt,max_retries,retry_delay_ms,error_status,error_category}
  • command_lifecycle: command_uuid, state{queued,started,completed,cancelled,discarded,refused} · system/session_state_changed: state{idle,running,requires_action} · system/status: status{compacting,requesting,null}, permissionMode, compact_result{success,failed}, compact_error
  • system/compact_boundary: compact_metadata{trigger{manual,auto}, pre_tokens, post_tokens, cumulative_dropped_tokens, duration_ms, user_context, messages_summarized, precomputed, pre_compact_discovered_tools, preserved_segment{head_uuid,anchor_uuid,tail_uuid}, preserved_messages{anchor_uuid,uuids,all_uuids}}, logical_parent_uuid, historical
  • system/notification: key, text, priority{low,medium,high,immediate}, color, timeout_ms · api_error: error{message,status,request_id,formatted,connection{code,message,is_ssl_error}}, retry_in_ms, retry_attempt, max_retries · api_retry: attempt, max_retries, retry_delay_ms, error_status, error, no_response · turn_duration: duration_ms, budget_tokens, budget_limit, budget_nudges, message_count, pending_background_agent_count, pending_workflow_count · vcs_state_changed: kind, cwd, branch · code_change_published: provider, url, repo, identifier, action, branch · away_summary: content · hook_started: hook_id, hook_name, hook_event · hook_response: + output, stdout, stderr, exit_code, outcome · thinking_tokens: estimated_tokens, estimated_tokens_delta, user_message_uuid · commands_changed: commands · stop_hook_summary: hook_count, hook_infos, hook_errors, hook_additional_context, prevented_continuation, stop_reason, has_output, level, tool_use_id, hook_label, total_duration_ms · model_refusal_fallback: trigger, direction, scope, original_model, fallback_model, request_id, api_refusal_category, saw_cyber_refusal, api_refusal_explanation, retracted_message_uuids, refused_user_message_uuid, content · control_cancel_request: request_id · set_in_progress_tool_use_ids: op · compact_progress: event · tombstone: message · keep_alive: —
  • Transcript-carrier twins (camelCase): api_error {error, retryInMs, retryAttempt, maxRetries, source}; turn_duration {durationMs, messageCount}; compact_boundary {content, compactMetadata}; stop_hook_summary {hookCount, hookInfos, hookErrors, hookAdditionalContext, preventedContinuation, stopReason, hasOutput, toolUseID}; model_refusal_fallback {originalModel, fallbackModel, requestId, apiRefusalCategory, apiRefusalExplanation, refusedUserMessageUuid, retractedMessageUuids}.

KNOWN_FIELDS (codex 0.153.4/0.154.0 rollout corpus, struct sizes from the binary): envelope {timestamp, ordinal, type, payload} · turn_context (struct 23; corpus 17): turn_id, cwd, workspace_roots, current_date, timezone, approval_policy, approvals_reviewer, sandbox_policy, permission_profile, model, comp_hash, personality, collaboration_mode, multi_agent_version, realtime_active, effort, summary, root_turn_id · session_meta: session_id, id, timestamp, cwd, originator, cli_version, source, model_provider, base_instructions, history_mode, context_window, parent_thread_id, thread_source, agent_nickname, agent_path, multi_agent_version, git, forked_from_id, subagent_history_start_ordinal · token_count: info, rate_limits · token_usage_record: thread_id, turn_id, session_id, root_turn_id, response_id, usage, turn_token_usage, thread_token_usage · task_started: turn_id, started_at, model_context_window, collaboration_mode_kind · task_complete: turn_id, last_agent_message, started_at, completed_at, duration_ms, time_to_first_token_ms, error · turn_aborted (5): turn_id, reason, started_at, completed_at, duration_ms · thread_settings_applied: thread_id, thread_settings · item_completed: thread_id, turn_id, item, started_at_ms, completed_at_ms — items: CommandExecution{id,process_id,command,cwd,parsed_cmd,source,status,stdout,stderr,aggregated_output,exit_code,duration,formatted_output} · FileChange{id,changes,status,stdout,stderr} · Reasoning{id,summary_text,raw_content} · AgentMessage{id,content,phase,delivery,questions} · Extension{kind: web.search{query,action,results} | image_gen.generation{status,revisedPrompt,result,transparentBackground,failure,savedPath} | clock.sleep{durationMs}} · McpToolCall(struct 12){id,server,tool,arguments,status,result,duration,+connectorId,mcpAppResourceUri,linkId,appName,actionName,readOnlyHint} · SubAgentActivity{id,kind,agent_thread_id,agent_path,detail} · ImageView{id,path} · UserMessage{id,content,client_id} · CollabAgentToolCall{id,tool,status,sender_thread_id,receiver_thread_ids,receiver_agents,agents_states} · ContextCompaction{id} · response_item function_call{id,name,namespace,arguments,call_id,internal_chat_message_metadata_passthrough}, custom_tool_call{id,status,call_id,name,input,…passthrough}, reasoning{id,summary,encrypted_content,…passthrough}, message{id,role,content,phase,…passthrough}.

Scratch (not in the repo): /tmp/vs-census/ holds the census script, the extracted schema table (claude-schemas.json) and both string dumps.

4. CLI 2.1.281 census (2026-09-23)

What broke. The CLI auto-updated 2.1.280 → 2.1.281 and the §4 oracle read union 0 shapes · 0 system subtypes · 0 stream record types: five legs red, every push blocked. Nothing in the union changed shape — the minifier re-assigned its short names across the whole bundle (the zod object helper u( → d(, the lazy wrapper p( → f(, the boolean H() → O(), the enum V( → z(; the literal stayed R(), and the extractor had the pair u(/R( spelled into its regex. Fix: the extractor reads the helper pair off an ANCHOR — the system/compact_boundary object (else init / task_started), which exists only in that union — and walks every object literal spelled with that pair; an identifier guard in front of the helper keeps a method call ending in the same letter out (2.1.281's MCP content blocks carry QVt.extend({type:R("resource_link")}), which a bare d( matched as a fake shape). No anchor ⇒ an error naming the anchor, never 0/0/0. test-record-shape §4a pins the 2.1.280 and 2.1.281 spellings, a future rename, an unminified z.object/z.literal form and the no-anchor error.

2.1.281 census: 155 shapes · 52 system subtypes · 35 stream record types (2.1.280: 154 · 51 · 35; 2.1.274: 153 · 51 · 35 — the §1 table's "34" was the first census's count; the same extractor reads 35 on every build). 88 declared stream shapes checked. The delta, every item read off the binary's own describe() text:

  • New system subtype per_turn_effort_changed {per_turn_effort_active} (@internal): sent once, before the retried request, when the server refused the per-turn effort message — from then on an effort switch rewrites the cached prefix (full-price cache writes) for every model until a later system/init. Sent on the stream-json output of headless (-p/SDK) sessions — the wire VibeSpace's chat sessions read. Routed to DECLARED_UPSTREAM_UNSEEN (the fall-back Unknown-event card shows it); it is a COST fact a user may need — a dim notice plus a cache-rewrite warning on the effort picker is the proposal, not built. Its shape row is 2.1.281's own declaration; SHAPE_SINCE lets an older installed CLI pass the reverse (dead / ghost) legs.
  • system/init + per_turn_effort_active (whether an effort change keeps the prompt cache for this frame's model; absent = unknown) + view_mode (focus|default, the /focus view: the model is told the user sees only the final message per turn). Declared ignored.
  • assistant + local_command_outcome ({kind: unavailable_headless|unknown|failed|restart_required, suggestion?} on a local slash command's twin row, so a host can offer a fix). Declared ignored (assistant rows are judged by content-block type at runtime; the oracle is the only reader).
  • conversation_reset + trigger (clear|plan_mode_exit|fresh_session|onboarding, informational) + user_message_uuid (with clear only: the /clear message's uuid) + timestamp (ISO, for a "conversation cleared" row). Declared ignored; the type stays declared-unseen.
  • Not record shapes (no action): the MCP server config shapes stdio / sse / ws gained bareElicitationCapability.
  • No field was removed from any shape; no top-level stream type was added or removed.

5. CLI 2.1.288 census (2026-10-07, lane cli-2-1-288-records)

2.1.288: 170 shapes · 61 system subtypes · 35 stream record types (dump: scripts/fixtures/claude-cli/2.1.288.json, method in scripts/claude-cli-dump.mjs). Eight new subtypes, each decided on its zod literal: instruction_size_warning → HANDLED (dim notice, our words); session_metadata, ui_toast, ui_status, ui_log, ui_invalidate, ui_scroll, ui_focus → KNOWN_IGNORED (+ ui_panes, spelled outside the union, CORPUS_ONLY). 23 new fields on six known shapes → FIELDS_288 (ignored, named). The oracle is pinned per version (SCHEMA_CLI_VERSIONS 2.1.281 + 2.1.288, one tracked dump each, test-record-shape §4e).