# User verification cancellation (experimental) Local UI clients can cancel a native user-verification RPC by sending `userVerification/cancel` with `{requestId}` and the `experimentalApi` opt-in. The result is an empty acknowledgment (`{}`). This API does not enable desktop verification capability advertisement. `requestId` is the original status, enroll, delete, or verify RPC's string or integer ID on the same connection, not the server elicitation ID. Use fresh IDs for each operation and a distinct ID for the cancel RPC. Unknown, finished, unrelated, and other-connection requests are no-ops. The acknowledgment confirms the cancellation signal without waiting for the OS prompt to close. The original RPC completes independently, with `cancelled/interrupted` when cancellation prevents completion. Cancellation cannot roll back completed effects. It remains effective while a proof waits for outbound queue capacity, but cannot retract a response already enqueued. Canceling or resolving an elicitation does not itself stop a separate `userVerification/verify` RPC. Clients must cancel that RPC separately and discard late proofs after the approval is canceled or resolved. Only one native worker runs per app-server; if an OS call remains active after cancellation or timeout, subsequent local operations return `failed/providerError` until that worker exits. # Hosted Codex Apps MCP protocol The host-owned HTTP `codex_apps` server uses Legacy by default in app-server and standalone Codex. To discover the 2026-07-28 protocol, set `codex_apps_mcp_2026_07_28 = true` under `[features]`, or send a true runtime override via `experimentalFeature/enablement/set`. Discovery falls back to Legacy when the server does not support it. Explicit config takes precedence. The dedicated setting does not apply to third-party HTTP or local `codex_app` stdio servers. The existing `mcp_2026_07_28` flag still governs eligible other servers, regardless of whether their names or URLs resemble hosted Apps. App-server does not persist this selection. # Thread removal `thread/archive` and `thread/delete` reject attempts to remove a live internal worker with JSON-RPC error `-32600`. The worker's owner controls its shutdown. For example, a Guardian reviewer remains available to its parent conversation after a client tries to archive or delete it. After the owner releases the worker, its saved conversation can be archived or deleted normally. Ordinary client-controlled threads keep their existing behavior. ## User verification (experimental) Codex app-server advertises `openai/elicitation.userVerification` to the host-owned plugin service for bundled, in-process TUI sessions (`codex-tui`) and local stdio desktop sessions (`Codex Desktop`) on devices with supported biometric hardware and the `experimentalApi` opt-in. This is an app-server decision, independent of whether a key exists; TUI/Desktop/mobile do not advertise this MCP capability. Mobile integration requires a separate rollout. Other clients and network connections do not receive this mode, even with a recognized client name. Before sending verification requests to desktop sessions, deploy a GUI that handles the typed verification request, cancellation, and late proofs. The general `experimentalApi` opt-in does not identify a compatible GUI version. Local UI clients use five methods. They require the existing `experimentalApi` opt-in. The local provider reports `unavailable/providerUnavailable` on unsupported platforms or without the required ChatGPT account identity. | Method | Params | Result | | --- | --- | --- | | `userVerification/status` | `{}` | `{credentialId, unavailableReason, unavailableMessage}` | | `userVerification/enroll` | `{}` | `{credentialId}` | | `userVerification/delete` | `{}` | `{}` | | `userVerification/verify` | `{challenge, title, description}` | `{proof: {credentialId, signature}}` | | `userVerification/cancel` | `{requestId}` | `{}` | Status reads local readiness without prompting or contacting a backend. A null `unavailableReason` means local checks passed, not that registration is valid. Unsupported platforms and missing account identity are reported in the status response's `unavailableReason` field. The initial enrollment creates or reuses the local key only. Backend registration and revocation are integration TODOs; local success is not server enrollment. Deletion currently removes that local key synchronously. Enrollment and deletion coordinate credential lifecycle; callers do not issue separate generate or rotate commands. Identity comes from the authenticated account; this API exposes no caller-selected scope. Verify signs 1–4096 decoded challenge bytes using P-256 ECDSA with SHA-256. The challenge and DER signature use unpadded base64url. Title is 1–256 UTF-8 bytes; description is at most 4096 bytes. The UI obtains approval for that display context before calling. Verify does not require a pending elicitation; a UI with its own authenticator can return proof directly in elicitation response content. The calling flow owns pending-request checks and discards late proofs. Native enroll, delete, and verify accept local stdio and in-process connections. WebSocket and remote-control peers must use their own device authenticator; status remains available for local readiness. Dropping an embedded RPC, disconnecting, or changing authentication cancels its native operation. Responses recheck the captured identity after waiting for outbound queue capacity. Canceling or resolving an elicitation does not itself stop a separate `userVerification/verify` RPC. The GUI must use `userVerification/cancel` to cancel that RPC and discard late proofs when an approval is canceled or resolved. See [User verification cancellation](#user-verification-cancellation-experimental) for request ID and acknowledgment semantics. Only one native worker runs per app-server. If an OS call remains active after cancellation or timeout, subsequent local operations return `failed/providerError` until that worker exits. Failures use the normal JSON-RPC error envelope with closed `{type, reason}` data: `invalidRequest`, `unavailable`, `cancelled`, or `failed`. UI clients branch on these values rather than message text. Native diagnostic payloads stay private. # Amazon Bedrock authentication If `model_providers.amazon-bedrock.aws.credential_export` is configured, Bedrock setup and Bedrock login return an error without changing configuration or saved credentials. Remove the exporter configuration before selecting another credential source. `aws.credential_export` and `aws.profile` cannot be configured together. ## Stored thread attachments - `thread/attachment/add` — add a durable resource reference to a stored thread without loading it. Repeated writes with the same attachment type and identity key return the existing attachment. - `thread/attachment/list` — list attachments for one stored thread in a cursor-paginated request, including a thread that is not loaded. - `thread/attachment/remove` — remove an attachment by its thread, attachment type, and identity key; returns `{}`. - `thread/attachment/updated` — notification broadcast after an attachment is created or removed; contains the thread, attachment identity, attachment id, and operation. ### Example: Manage stored thread attachments Attachments record the resources currently associated with a thread, independently of conversation history. Clients can add, remove, and list attachments for one stored thread at a time without resuming those threads. Adding or removing an attachment does not create or delete the underlying resource or rewrite history. An attachment is idempotently identified by its thread, `attachmentType`, and `identityKey`. For pull requests, clients should reuse the canonical application identity `JSON.stringify([canonicalHostname, lowercaseOwner, lowercaseRepository, pullRequestNumber])` so addition and removal agree across surfaces. ```json { "method": "thread/attachment/add", "id": 20, "params": { "threadId": "thr_123", "attachmentType": "pull_request", "identityKey": "[\"github.com\",\"openai\",\"codex\",123]", "payload": { "url": "https://github.com/openai/codex/pull/123" } } } { "id": 20, "result": { "outcome": "created", "attachment": { "id": "01984de2-8f74-7c91-a3b2-5c5e937cf318", "attachmentType": "pull_request", "identityKey": "[\"github.com\",\"openai\",\"codex\",123]", "payload": { "url": "https://github.com/openai/codex/pull/123" }, "createdAt": 1750000000 } } } { "method": "thread/attachment/list", "id": 21, "params": { "threadId": "thr_123", "limit": 100 } } { "id": 21, "result": { "data": [{ "id": "01984de2-8f74-7c91-a3b2-5c5e937cf318", "attachmentType": "pull_request", "identityKey": "[\"github.com\",\"openai\",\"codex\",123]", "payload": { "url": "https://github.com/openai/codex/pull/123" }, "createdAt": 1750000000 }], "nextCursor": null } } { "method": "thread/attachment/remove", "id": 22, "params": { "threadId": "thr_123", "attachmentType": "pull_request", "identityKey": "[\"github.com\",\"openai\",\"codex\",123]" } } { "id": 22, "result": {} } { "method": "thread/attachment/updated", "params": { "threadId": "thr_123", "attachmentType": "pull_request", "identityKey": "[\"github.com\",\"openai\",\"codex\",123]", "attachmentId": "01984de2-8f74-7c91-a3b2-5c5e937cf318", "operation": "deleted" } } ``` `thread/attachment/list` accepts one `threadId` and returns at most 100 attachments per page, ordered by creation time and attachment id. Continue with `nextCursor` and the same `threadId` until the cursor is `null`. Each thread can retain up to 100 attachments. Removing an attachment frees a slot for a new attachment. Attachment creation and deletion requests using the same thread ID are serialized across connections. The requesting client receives its response before the compact update is broadcast, and duplicate creates or absent deletes do not emit updates. Deleting the owning thread removes its attachments under the same lifecycle exclusion; queued attachment mutations then report that the thread was not found.