# Proof audit, pass 1: Claude process exit diagnostics

PR [#26086](https://git.cafe/pingdotgg/t3code/pull/26086) ([GitHub mirror](https://github.com/pingdotgg/t3code/pull/26086)); branch `fix/issue-26082`.
Reviewed head [652e9c1](https://github.com/pingdotgg/t3code/commit/652e9c18cee800df7e255916365967ba211097ae), base/merge-base [d1c4b52](https://github.com/pingdotgg/t3code/commit/d1c4b52b7c7c7e1b7637ec47e4c959ff0c2d8d99). Read-only review in `/home/ubuntu/wt/pr-26086`; audit artifacts only in `/tmp/lead-26086-r1/proof-audit-1/`.

## Findings first

1. **Proof is incomplete, not a confirmed implementation defect.** The production diff is 21 server lines plus one regression test. It changes failure message/code, with no client render implementation changed. The installed SDK `0.3.276` (`claudeCodeVersion 2.1.276`) confirms the two error classes and telemetry fields. An auth-free subprocess probe also reproduced both real SDK errors. The committed test covers a wrapped SIGABRT cause and excluding stderr; it does not assert `failure.code`, nonzero exit, malformed metadata, or fallback behavior. Do not count that test or the SDK probe as visual/app proof.
2. **The claimed “toast” needs precise naming.** The linked PNGs show the existing floating **thread error banner**, plus the timeline row. Actual background in-app notification toasts and desktop notifications use **thread.title**, not failure.message. Their text is unchanged. The PR's statement that clients compare code *only* to the workspace constant is also too broad: the web error banner and mobile usage notice compare ChatGPT usage codes. Neither new process code matches those codes, so this does not expose an implementation bug.
3. **The changed content has more consumers than the lead's summary names.** Besides the web/desktop timeline and banner, mobile shows it in the feed and active/card thread-list preview. An app-owned delegated Claude turn also propagates it through `subagentResultForRun` into the parent's subagent result: web timeline/notification links and tooltips, and mobile subagent cards/Agents sheet. Those are included below. Native workflow member results are a separate SDK-message path, not the app-owned turn-failure propagation; do not manufacture unrelated workflow states just to enlarge this audit.
4. **Earlier evidence is genuine but narrow.** I downloaded and inspected both 1280×800 light PNGs and the 590-byte transcript. They visibly show `Provider turn failed.` before and `Claude Code process terminated by signal SIGABRT.` after, in both banner and error row. The transcript contains SIGABRT stream-failure log excerpts and UI text at the claimed earlier revisions. It contains no code assertions, nonzero exit, mobile, desktop-notification, or timing proof. The adapter/test and relevant web render files are unchanged between the old evidence revisions and the reviewed revisions, so the historical static comparison remains useful; it is not a recording or proof of every current surface.

## Data boundary and source map

- [Adapter extraction](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts#L629): unwrap a query-runner error, require object + allowed string errorClass + string telemetryMessage. Emits `${telemetryMessage}.` and errorClass. A recognized signal example is `{class:'transport_error',message:'Claude Code process terminated by signal SIGABRT.',code:'process_killed_by_signal',retryable:null}`. Exit example replaces message with `Claude Code process exited with code 7.` and code with `process_exited_nonzero`. Ordinary Error / clean EOF / unsupported or malformed metadata takes existing failure construction, normally `Provider turn failed.` and null code. Pre-existing curated domain-error behavior is not changed.
- [Finalizer](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts#L7394): only the current active turn is failed; stop-requested turns remain interrupted; a query with no lastTurn does nothing. Old/replaced queries cannot fail a replacement turn. [Ingestion](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/server/src/orchestration-v2/ProviderEventIngestor.ts#L702) makes one failed error item and retains the existing OOM override (`Killed: out of memory.`, `oom_kill`). Do not induce real OOM for captures.
- [Failure item construction](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/packages/provider-core/src/server/failure.ts#L233) fixes title `Provider error`, status failed, no retry metadata here. Message/code remain compatible with the [existing contract](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/packages/contracts/src/orchestrationV2.ts#L1354). Code is stored, not printed as a new badge.
- Web/desktop: [timeline projection](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/session-logic.ts#L362) -> work entry -> both callers of `SimpleWorkEntryRow` in [TimelineWorkGroupRows](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/chat/TimelineWorkGroupRows.tsx#L100) -> [failed row](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/chat/TimelineWorkEntryRow.tsx#L199). Label is red Provider error; message is always visible, wraps, timestamp remains visible. No error-row expansion/collapse interaction. Usage-limit hiding/reset time and workspace Retry branches are not entered by these process failures.
- Web/desktop banner: [threadErrorSummary](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/packages/shared/src/orchestrationV2ThreadError.ts#L15) -> client-runtime `deriveThreadRuntime` / server shell -> [ChatView](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/ChatView.tsx#L839) -> ChatBannerLane -> [ThreadErrorBanner](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/chat/ThreadErrorBanner.tsx#L72). Latest failed root run supplies message; session/local errors can supersede it. Dismissal is keyed by thread + message for the session. Error styling, no ChatGPT action. SDK exit sentences are plain, single-line, under 180 characters: `ExpandableText`'s JSON parsing / Show full error branch is not reached. Width can still wrap the sentence.
- Alternate web renderers exist in [V2EventTimelineRow](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/chat/TimelineV2EventRow.tsx#L54) and [V2ItemInspector](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/chat/V2ItemInspector.tsx#L413). They can render error details if explicitly supplied, but normal error items are **work**, not **event** (`STANDALONE_V2_ITEM_TYPES` excludes error), and failed work rows return before the inspector. No real process-exit fixture reaches those disclosures; no harness captures needed for dead alternatives.
- Mobile feed: ThreadFeed -> ThreadWorkLog -> [ThreadWorkLogRow](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/mobile/src/features/threads/thread-work-log.tsx#L882). Full selectable message, red label/icon, fixed timestamp, no Retry and no error disclosure. Accessibility label contains the new text; long press copies it and temporarily shows Copied.
- Mobile list: [ThreadListV2Row](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/mobile/src/features/threads/thread-list-v2-items.tsx#L1145) callers are HomeScreen (phone screen or wide primary sidebar) and ThreadNavigationSidebar. Failed card rows show lastError on **one line**, replacing environment label; PR/provider adornments reduce available width. Selected uses muted selected foreground, unselected uses danger foreground. iOS/Android have different row appearance files. Slim settled/snoozed rows omit this card content entirely. Web sidebar hover says only Error occurred, not the failure sentence.
- Delegated result: [subagentResultForRun](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/packages/provider-core/src/server/subagentProjection.ts#L202) -> [Orchestrator completion](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/server/src/orchestration-v2/Orchestrator.ts#L11290) copies failed child's message ahead of partial assistant text into task + subagent item result. Web [SubagentTimelineLink](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/chat/V2LifecycleRow.tsx#L388) is used by lifecycle rows and SubagentNotificationLink; shows single-line detail and failed status, plus tooltip. [SubagentTooltipContent](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/chat/SubagentTooltipContent.tsx#L54) is also used by ThreadRelationshipsControl. Mobile [SubagentRow](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/mobile/src/features/threads/SubagentRow.tsx#L36) is used by ThreadSubagentGroup and ThreadAgentsSheet; detail has up to three lines. Multiple children hide individual results until group expansion. `ThreadWorkflowOpening`/web workflow tooltip callers consume provider-native workflow metadata, not this app-owned failure result: excluded from minimum.
- [ThreadNotificationCoordinator](https://github.com/pingdotgg/t3code/blob/652e9c18cee800df7e255916365967ba211097ae/apps/web/src/components/ThreadNotificationCoordinator.tsx#L83): active focused thread -> no notification toast; other focused thread -> in-app toast `Thread failed` + thread.title; hidden/unfocused with permission -> OS notification `Thread failed` + thread.title. Off/denied -> none. Desktop uses the same web coordinator (plus revealWindow on click); no separate failure.code renderer was found. Mobile agent-awareness notification code does not directly render these failure fields.

## Seeding without Claude OAuth

No real Claude account is available here. That is **not** a reason to substitute a screenshot mock for the normal server path.

1. Use a disposable app home belonging to the capturer. Keep all driver outputs in a **new timestamped directory** under `/tmp/lead-26086-r1/proof-audit-1/`; copy real data in only if necessary. Never point a dev server at live userdata. This auditor started no server/browser and made no worktree changes.
2. In `/settings/providers?environmentId=<fixture-environment>&instanceId=<fixture-Claude-instance>`, set the destination environment's Claude **Binary path** to the absolute stub `.mjs` path and its homePath to a scratch directory. Alternative: read existing settings then patch that instance via `server.updateSettings` with `{patch:{providerInstances:{<id>:<preserved instance config with config.binaryPath/homePath changed>}}}`; preserve other fields. The setting is per provider instance/environment, not a web bundle origin. Direct, remote and tunnel clients consume the same server failure payload; use one remote client on this isolated environment for at least one native capture if available.
3. Stub protocol: answer SDK `control_request`s (initialize, mcp_set_servers, settings/model/permission queries), send init + assistant or partial stream frames for a user prompt, **do not send result**, wait for a recorder-visible partial response, write a nonsecret stderr sentinel, then self-signal SIGABRT or exit(7). Delay failure until after MCP registration and prompt delivery; killing at spawn can hit open retries instead of the modified active-turn path. Return a plausible version for `--version` and initialization metadata/models for provider discovery. No first-party OAuth is needed because the child is the fixture executable.
4. A starter exists at `/tmp/lead-26086-r1/proof-audit-1/claude-exit-stub.mjs`. `PROOF_MODE=signal|exit|clean`, `PROOF_SIGNAL=SIGABRT`, `PROOF_DELAY_MS=5000` are **fixture process** environment variables, not app settings. `PROOF_HOLD` in a prompt keeps that stub turn alive for parent/stop fixtures. Modes can instead be hard-coded into distinct `.mjs` wrappers so changing the app server's environment is unnecessary. This starter is validated only with the real SDK, not a full T3 server; extend discovery/control replies if the actual app requests more. It emits an assistant frame; for a visibly growing response emit SDK stream_event deltas before the assistant frame.
5. Driver can use authenticated `orchestration.launchThread` or `orchestration.dispatchCommand` (`thread.create`, `message.dispatch` with `dispatchMode:{type:'start_immediately'}`), then read `orchestration.getThreadProjection` / subscribeThread. Use the application's RPC client/codec, not guessed WebSocket envelopes. Persist IDs and failure JSON and record SDK version, app head, client, theme, viewport and exact route `/<environmentId>/<threadId>`. No secret/token in uploads.
6. For delegated fixtures, hold a parent run with `PROOF_HOLD`, then dispatch `delegated_task.request` with the real parent run/root-node IDs and the stub Claude selection. The child goes through the real adapter and completion service. Use `workspaceStrategy` local and avoid real agent work. One child for D1; two consecutive child tasks for D2. Reopen parent / Agents UI after persisted completion. This is fixture seeding, not permission to launch real delegate agents during the audit.
7. For ordinary-error fallback, malformed metadata, OOM override, missing active turn and owned-query guards, use the existing service/query-runner test layer in a scratch driver or read-only diagnostics; those are **labelled backend harness** checks. A clean child exit with no result can seed the ordinary active-turn fallback in the real app. A driver that injects final JSON into a UI proves rendering only, never the SDK→adapter path.

The SDK-only probe already ran (`sdk-exit-probe.mjs`); result is `/tmp/lead-26086-r1/proof-audit-1/sdk-probe-2026-10-27T19-17-34.313Z/result.json`. It saw system + assistant then real errors with precisely the expected telemetryMessage/errorClass for SIGABRT and exit 7; clean exit yielded no SDK exception. It captured stderr separately. With T3's custom spawn, stderr may be ignored (POSIX) rather than appended; the SDK source's default ProcessTransport explicitly appends its tail only to message. Absence of a tail in the probe is not proof that arbitrary stderr was sanitized by this PR.

## Fixture / capture matrix

`required` means the minimum proof still needed for an affected visual surface or the failure transition. `code-only` rows document unmodified/irrelevant alternatives and do not demand screenshots. Use one before/after pair for each affected visual layout, matching data and viewport; reuse the historical F1 pair. A recording's stable endpoint can supply that row's still without a second run. Stills cannot replace recordings for interaction/time rows. Native dimensions below are logical viewport targets; record actual device/screenshot dimensions and font scale 1.0. No all-themes × all-widths product is justified by this server-only diff.

| ID / named fixture and required data | Affected by diff? / expected visible result | Minimum viewport × theme × client; proof | Seed through real app / fallback | Status at pass 1 |
|---|---|---|---|---|
| F1 — latest failed root, SIGABRT after partial reply; one normal error item, no retry/resetAt, stderr sentinel | **Yes**: row + banner say `Claude Code process terminated by signal SIGABRT.`; label/time remain; no Retry/code badge | 1280×800 × light × web; **still** before/after | Stub signal; keep thread selected at `/<env>/<thread>`; wait for persisted failed run, then static capture | **Historical static pair VERIFIED**; same adapter/render code. Does not cover T1 |
| F2 — latest failed root, exit 7; same data shape, narrow wrapping | **Yes**: row + banner say `Claude Code process exited with code 7.`; fit narrow pane, no extra actions | 390×844 × dark × web; **still**, required (may extract from I1) | Stub exit mode, independent thread; same route | **PENDING app capture**; SDK-only error verified |
| F3 — ordinary stream failure / clean EOF without result | **No new presentation**: generic `Provider turn failed.`; no process code; no stderr | Runtime transcript + projected failure JSON; no additional viewport/theme needed (same F1 before layout) | Clean stub exit in app; unsupported/malformed metadata via labelled query-runner harness. Compare exact message/code | **CODE VERIFIED; runtime/app control pending**; no redundant UI still required |
| T1 — active partial reply → unexpected process exit → failed idle/composer ready | **Yes, content**; no stuck Working, exactly one error row; partial answer retained if emitted | 1280×800 × dark × **desktop**; **recording**, required. Signal, with exit 7 repeated in driver transcript | Stub delay after user prompt; capture running state and failure; record persisted code/message. Desktop is the production web renderer inside Electron | **PENDING recording**; historical PNGs cannot prove timing |
| I1 — banner visible → dismiss → switch away/back → next message/run | **Yes, content keyed dismissal**: correct sentence disappears from banner, row remains; revisiting doesn't replay the dismissed banner in the session; next active run shouldn't inherit stale latest-failure banner | 390×844 × dark × web; **recording**, required; F2 endpoints can be reused | Seed F2; click Dismiss error; navigate away/back; dispatch a held second turn (or successful stub extension). Do not claim page-reload dismissal persistence; module state resets on reload | **PENDING recording** |
| M1 — native failed feed, signal/exit, full selectable text, no retry | **Yes**: new sentence wraps as needed under Provider error; accessibility/copy retains it | iOS 390×844 × dark + Android 412×915 × light; **recording**, required because long-press copy/Copied feedback is included | Same isolated server failures; open Thread screen, long press failure row, verify clipboard text. Use signal on one OS and exit 7 on the other | **PENDING native recordings**. If native app/device genuinely unavailable to capturer, labelled production RN-component harness only, with explicit limitation; OAuth absence alone doesn't justify harness |
| M2 — mobile Home **card**, failed, unselected; one-line preview with provider icon and optional PR adornment | **Yes**: lastError preview replaces environment label; truncates without growing card | iOS 390×844 × light + Android 412×915 × dark; **still**, required; can be endpoints of M1/M3 | HomeScreen for failed un-settled thread; keep optional PR absent for minimal case (adding unrelated PR decoration not required). Native app on same fixture server | **PENDING native stills** |
| M3 — mobile sidebar/primary-column **card**, selected ↔ unselected | **Yes**: same preview, selected muted foreground versus danger foreground; narrow available width | Android 412×915 × dark sidebar + iOS 390×844 × light sidebar; **recording**, required; phone drawer suffices, tablet geometry not a new failure branch | ThreadNavigationSidebar: open drawer with failed thread selected; select another thread and reopen; show same failure preview both ways. Home wide primary-column uses same card data/render path | **PENDING native recordings** |
| M4 — settled/snoozed mobile **slim** row; failed history retained | **No**: slim omits cardContent, so failure sentence is not rendered | No extra capture required; if exercising settle/snooze, **recording** | `thread.settle`/`thread.snooze` via RPC or UI, inspect slim shelf; `thread.unsettle`/`thread.unsnooze` restores card | **CODE VERIFIED / excluded from minimum** |
| D1 — one app-owned delegated Claude child fails, with partial assistant text and process-exit failure | **Yes, indirectly**: parent's subagent result prefers failure sentence over partial reply; row detail + tooltip/sheet show it | Web 1280×800 × dark; mobile iOS 390×844 × light and Android 412×915 × dark; **recording**, required | `delegated_task.request` from held parent; inspect single subagent timeline row, web hover (also SubagentNotificationLink if delivered); open web relationships and hover same child; native open Agents sheet and child link. Same SubagentTooltipContent/SubagentRow covers repeated callers | **PENDING app captures**; source propagation verified |
| D2 — two adjacent app-owned delegated children, one failed, one failed/completed; group collapsed → expanded | **Yes when revealed**: collapsed count/status hides detailed sentence; expanded failed member shows it; no need 3/4/5 child overflow permutations | Web 1280×800 × dark; mobile one of M1 device/theme pairs; **recording**, required | D1 driver requests a second adjacent child task; expand parent subagent group, hover/open failed child; native same ThreadSubagentGroup expansion. Empty/no-child has no failure to render | **PENDING app captures** |
| H1 — historical failed turn, later current/held run; inherited/forked history if exposed | **Yes for stored error text**, no latest-error banner from historical item; row remains readable. Visibility attributes alone do not change this failed-row layout | No extra viewport; include historical-row endpoint in I1; **recording** for navigation/new turn | Reuse I1 second run; scroll older error. A normal fork is optional since failed WorkEntryLogRow has no visibility-dependent visual branch | **PENDING I1 coverage**, not a separate capture session |
| N1 — focused active thread / focused other thread / unfocused desktop; notifications granted vs off/denied | **No new notification content**: active no toast; other-thread toast and OS notification `Thread failed` + thread.title; never failure.code/sentence | **Code-only** for minimum. If checking the PR's literal toast claim at runtime: web/desktop native viewport, **recording** (focus, timing, click Open thread) | Set in-app notifications on and OS mode/permission; stub failure while viewing another thread, then with window unfocused; click to reveal/navigate. If headless VM has no OS UI, labelled notifier harness only | **CODE VERIFIED; runtime optional**. Floating F1/I1 banner is not this toast |
| X1 — usage-limit/retry/resetAt/workspace-preparation and ChatGPT-code branches; empty/loading error fields | **Not generated by this change**: class is transport_error, status failed, nonempty SDK sentence, retry/resetAt absent, process codes match no special action | No capture | Existing fixtures only if another change touches these branches; don't seed synthetic process failures with impossible retry/usage metadata | **EXCLUDED by reachable shape** |
| X2 — V2EventTimelineRow error disclosure / V2ItemInspector, native workflow members, web generic sidebar hover | **Not an additional changed real-app render path**: normal errors route to failed work row; provider-native workflow result comes from SDK workflow metadata; generic sidebar hover doesn't show error sentence | No capture; if explicitly demonstrating alternate component, label **harness**, not real process-exit evidence | No need to manufacture unreachable event layout. Ordinary related-child tooltips are already D1; workflow metadata remains outside this diff | **CODE VERIFIED / excluded from minimum** |
| X3 — stop-requested process exit, no active/last turn, old replaced query, open-before-attach failure, OOM | **Unmodified guards/override**: interrupted remains interrupted, no false failed item; OOM keeps its override; pre-attach/open errors are not a claimed new-message path | Backend transcript/harness only; if showing Stop in UI, **recording**; no additional visual minimum | Existing query-runner/service fixtures, or held stub then explicit Stop. Never force real OOM. A resumed query **after attach** is equivalent to F1/F2; dying during open may retry/recover instead | **SOURCE REVIEWED**, not newly runtime verified |

### Minimum remaining package

- F1 historical still pair is accepted as static coverage, with its original revision labels.
- F2 + I1 + H1 can share one narrow dark web recording and its extracted endpoints.
- T1 is one desktop recording plus a second-mode (exit 7) backend transcript; no second desktop layout matrix.
- M1/M2/M3 can share one native recording session per OS, changing theme only for Home/sidebar endpoints. Record native surfaces honestly; no web viewport masquerading as React Native.
- D1/D2 can share one seeded parent family and recordings of single child, grouped collapsed/expanded, tooltip/Agents-sheet and navigation. Desktop need not repeat web subagent rows; Electron shares these components and T1 covers the shell.
- Attach driver JSON asserting both exact failure codes and absence of stderr sentinel. Backend control F3 should produce a short runtime transcript; malformed/unsupported shapes may be labelled harness checks. A screenshot cannot establish failure.code or SDK provenance.

## Existing evidence references

[Runtime transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b7cea28a9e31f5fc/runtime-transcript.txt).

Before — main [3a87681](https://github.com/pingdotgg/t3code/commit/3a87681d95):

![Before: light web error row and floating banner say Provider turn failed](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/4fcb3cd9383b58d6/before-sigabrt-mid-stream.png)

After — pre-merge [77bdd3a](https://github.com/pingdotgg/t3code/commit/77bdd3a145):

![After: light web error row and floating banner name SIGABRT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/6f6985875a47a735/after-sigabrt-mid-stream.png)

## Coverage and friction

Covered complete PR diff, SDK ProcessTransport/getProcessExitError, adapter finalization/query ownership, persisted failure construction/OOM precedence, thread summary, web/desktop/mobile direct consumers and app-owned delegation propagation, historical evidence and current PR metadata/comments/reviews. No browser/native-app sessions, no repo-wide checks, no new app captures, no posts/edits/pushes. SDK diagnostic children terminated; no server was started. Free disk stayed above 25 GB (initial 26 GB).

FRICTION:
- forge | Initial GitCafe view/status/comments/reviews returned ConnectionRefused; retry succeeded, with one later review read also succeeding on retry | reliable git.cafe connectivity.
- infra | Earlier evidence host first returned 403 / DNS resolution failure; later retry retrieved transcript and both PNGs | stable DNS/egress for the artifact host.

VERDICT: INSUFFICIENT F2, T1, I1/H1, M1–M3, D1–D2 and F3 app/runtime control remain to capture.
