codex-in-claude v0.16.0
A discovery-metadata and envelope-slimming release. Every tool now states its cost, its title, and
its stability tier in tools/list itself; codex_capabilities gains a summary default and a
contracts mode, so a client can fetch a schema or recheck a fingerprint without re-reading the
whole tool inventory; codex_job_list gains limit/status filters; and each codex:// resource
carries triage metadata a client can read before spending context on the body. Delivered success
envelopes stop sending explicit null meta members, and meta.roots_source now reports what the
MCP-roots probe actually saw on every envelope that reports it. Several capability and
documentation contracts that described something other than what was being sent are corrected. The
agent-visible surface changed ten times (result fingerprint codex-in-claude/0.1/schema-56 →
schema-66, and the persisted RESULT_FORMAT 6 → 7), so pre-1.0 this is a minor release;
clients that cache by fingerprint re-fetch the contract. Every change is backward-compatible — no
tool, field, or error code was removed or retyped.
Added
-
codex_capabilitiesacceptsdetail="contracts", which omitstool_detailsand returns
everything else unchanged. Theinclude_schemasfallback for resource-blind clients previously
re-sent the whole tool inventory on every schema fetch; pairing it withcontractsdrops a
schema fetch from 30,404 to 23,082 bytes, anddetail="contracts"alone is a 3,787-byte
fingerprintrecheck for cache revalidation (was 11,109).tool_detailsis the only field
removed, and it is already optional in the published schemas, so acontractsresponse still
validates against both the tool'soutputSchemaandcodex://capabilities-result— no schema
change was needed. The new token lives on a separateCapabilitiesDetailLiteral used only by
this tool; the sharedDetailstays two-valued, so the five other tools takingdetailare
unaffected. Backward-compatible:summaryremains the default and both existing modes are
unchanged. -
Every tool now states its cost in its own description — five tools that previously relied
oncodex_capabilitiesalone (codex_consult,codex_review_changes,codex_delegate,
codex_job_result,codex_job_consume_result) now sayPAIDorFreeexplicitly, and two
more (codex_dry_run,codex_delegate_dry_run), which already statedNO model call and no spendin prose, were normalized onto the sameFreetoken — so a client reading only
tools/listgets one consistent cost marker across every tool. -
Every cost marker now uses one canonical token —
PAID —for active tools,Free — no model callfor free ones — instead of near-miss variants (FREE —, a line-wrapped
Free —, or bare mentions of "spend").codex_transfer,codex_capabilities,
codex_consult_async,codex_review_changes_async, andcodex_delegate_asyncgain the
literal marker; the three asyncPAID —blocks each point at the correct preview tool
(codex_statusonly forcodex_consult_async,codex_dry_runfor
codex_review_changes_async,codex_delegate_dry_runforcodex_delegate_async), and all
six active tools now say "every new call" rather than "every call" so the marker doesn't
contradictidempotency_key's no-new-spend replay semantics. -
Every tool now carries a
titlefor human-facing pickers and a namespaced
_metastability tier, so a client reading onlytools/listcan see which tools are
experimental. -
codex_job_listgained optionallimit(1-1000) andstatusfilters to narrow the
returned jobs. Both are purely opt-in: omittinglimit(the default) still returns every
job the store retains, so the tool every error's repair hint names as the way to recover
a lostjob_idnever hides a row the server deliberately kept — the job store's own
retention policy is the only server-side limiter, and a soft one, since running jobs are
exempt from eviction. Only an explicit
limitcan truncate; when more jobs match, the response setstruncated: truewith a
truncation_hintpointing back at omittinglimit(or narrowing withstatus) — the
extra rows are dropped, not paged, so there is no cursor. Running jobs are never evicted,
so a busy workspace can retain more rows thanlimit's 1000 ceiling can ask for, which is
why omitting it is the only complete listing. The tool'scodex_capabilitiesrecord
advertises both params and the cap-not-a-page semantics, so a client treating the
detail="full"inventory as authoritative sees them too (audit F5, #396, #395). -
meta.roots_source(and the matching field oncodex_dry_run/codex_delegate_dry_run)
reports which of three states the MCP-roots probe saw:client(the client advertised the
roots capability and the probe returned, possibly an empty list),not_negotiated(this client
never advertised the roots capability — passworkspace_rootinstead), orprobe_failed
(roots were advertised but the call errored this turn — retrying may help). It reports the
probe, not where the workspace came from —workspace_sourceanswers that. Previously all
three collapsed into a silent empty list and a fallback to the server's own cwd;rootsstays
advisory either way, andworkspace_rootremains the durable path (audit F8). Which run the
value describes now depends on the envelope, andcodex://result-metastates the rule: when
present, a DELIVERED consult/review/delegate result reports the ORIGINATING run (like
meta.tier) whether returned synchronously or fetched later, while a*_asynchandle, a
dry-run preview, and an error acodex_job_*call generates instead of delivering a stored
result each report the CURRENT call — so a replay handle and the result later fetched for the
same job may legitimately differ. Absence implies none of that: it means only that the value
was not reported (an unexpected server-sideinternal_error, for instance, is built without
one), so never infer a run's age or identity from a missing key. Bumps the persisted result-format (RESULT_FORMAT6→7)
for the newMetafield; not breaking. -
Each
codex://resource now carries a namespaced_meta["dev.bconnelly.codex-in-claude/triage"]
so an agent can decide whether a body is worth the context before reading it: the five static
schema resources declaresize_bytes(computed from the payload at registration, so it cannot
drift from the body);codex://models, whose body is a refreshed cache, declaresvolatile: truewith afreshness_viapointer to the payload'sfetched_atfield instead of a size that
would go stale (audit F4).size_bytesnow counts the UTF-8-encoded bytes rather than
len()of the JSON string (Copilot review of #385): today's payloads are pure ASCII under
json.dumps's defaultensure_ascii=True, so the count is unchanged for all five resources,
but the name is now true by construction instead of by coincidence of that default.
Changed
- Delivered
codex_consult/codex_review_changes/codex_delegatesuccess envelopes now omit
metamembers whose value is null, instead of sending a run of explicit null keys on every
call — between 5 and 17 of them across the representative envelopes in
tests/fixtures/wire_shape_snapshot.json, though a real call's count varies with which
members apply to it.
A key's absence means exactly what the null meant — not applicable, or not reported for this
run — so readmetawith a null-safe accessor rather than by testing key presence. Measured
against those same representative envelopes: ~6–37% smaller atdetail="summary", and the
same ~6–37% atfull; the saving scales with how many members the run leaves
inapplicable, so the sparsest envelope gains most. The six requiredmetamembers are
always present and empty arrays stay empty arrays; everything outsidemetais delivered
verbatim, so top-level fields (includingcodex_delegate'sdiff, which is null when a run
proposes no changes) and all ofraw_responsekeep their keys. The*_asyncjob handle and
codex_job_statusare unaffected. Trimming happens on delivery, so the storedresult.json
is byte-for-byte unchanged (RESULT_FORMATstays7and already-stored job results stay
readable) and a replayed result still matches a fresh synchronous one. Not breaking: the
published output schemas andcodex://result-metaalready accept absence for every affected
field. BumpsFINGERPRINT(schema-60→schema-61); the rule is published on
codex://result-meta(#334). codex_capabilitiesnow defaults todetail="summary", returning only the per-tool facts
tools/listdoes not already carry (name,cost,stability,error_codes, and, for the
*_asynctools only,async_lifecycle). Passdetail="full"for the previous payload. The
extra_contextparameter contract moved its full text tocodex://paramsand
idempotency_key's inline summary was compressed. The durable size win iscodex_capabilities'
own response — 21,763 → 11,109 bytes (−49%) — but that is paid only by clients that call the
tool, not by every client the waytools/listis.- Net effect on the preloaded discovery surface: this release's other additions (per-tool cost
markers, titles, stability tiers,codex_job_listfilters,roots_sourceprovenance,
resource triage metadata) outgrew the compression above for every client, not just
codex_capabilitiescallers.tools/listwent from 79,242 to 83,895 bytes (+5.9%). That is a
deliberate trade: a larger preloaded surface in exchange for cost/stability/next-step metadata
that was previously missing or unreachable.
Fixed
-
codex_capabilitiesdelivered the per-toolstabilitykey in one detail mode but not the
other (#399).detail="summary"force-addedstability: nullfor the eight default-tier tools,
whiledetail="full"had it stripped byexclude_none— so a client moving from the default mode
to the richer one lost a key, contradicting the docstring's framing offullas additive. The
key is now forced once, before either mode branches, sofullcan no longer drop a key
summarycarries and a single code path is what putsstabilityon both;
detail="contracts"carries no inventory and is unaffected. Null keeps its published meaning — the tool inherits the server-widestability— and
the field stays optional in both published schemas, so this only adds key presence:
FINGERPRINTmoves (schema-65→schema-66),RESULT_FORMATdoes not, and it is not breaking.
The subset regression no longer carvesstabilityout and now also pins that both modes describe
the same tools and agree on every shared value. -
The documented contract misdescribed
detailrejection androots_source(#397). Three
corrections, none of which changes behavior. (1)docs/REFERENCE.mdtold direct MCP callers that
an unrecognizeddetailvalue is rejected withunsupported_detail; it is not reachable that way
—detailis a closed enum in each tool's input schema, so the call boundary rejects the value as
invalid_argumentsbefore the handler runs, andunsupported_detailstays deliberately
unadvertised as an in-handler guard for direct Python callers. A parametrized regression now pins
the boundary behavior across the five tools sharing the resultDetailenum, so the corrected
sentence cannot go stale unnoticed (codex_capabilitieshas its own three-valued
CapabilitiesDetail; pinning that one is #398's item 3). The same published description no longer
offers an unsupporteddetailas an example of a lifecycle-generated error, since over MCP that
rejection never reaches the handler. (2)roots_sourcewas absent fromREFERENCE.mdentirely,
including the workspace-selection section that owns workspace resolution; it is now documented
there with the probe-versus-selection distinction and the correct placement note that the two
dry-run tools expose it top-level on a successful preview, while their error envelopes carry it
undermetalike every other tool. (3) The publishedcodex://result-metadescription
saidclientmeans roots "were used", which conflates the roots probe with workspace selection —
clientonly reports that the probe returned, and it coexists normally with
workspace_source: "param"(an explicitworkspace_rootwins) or"cwd"(the probe returned no
usable root). Wording only, so it bumpsFINGERPRINT(schema-64→schema-65) without moving
RESULT_FORMAT; not breaking. -
meta.roots_sourcenever reached a delivered paid success envelope — the one surface it
was added for. A synchronouscodex_consult/codex_review_changes/codex_delegatesuccess is
delivered from the worker-writtenresult.json, not from themetathe handler prepared, and
the job spec never carriedroots_source, so the worker storednulland the null-omission
rule then dropped the key entirely. The three job specs now carry it and the worker reads it
back, so a paid success — and a paid failure, whose stored envelope is built the same way —
reports the roots state its run actually saw. The same value now also reaches the
lifecycle-generated error envelopes (job_not_found, a running/corrupt/incompatible job, and
codex_job_cancel— plus the in-handlerunsupported_detailguard, which only a direct Python
caller can reach, since over MCP a baddetailis rejected asinvalid_argumentsbefore the
handler runs), which probed roots and then discarded the answer;
those report the CURRENT lookup, not the inspected job's originating run.roots_sourceis
provenance rather than call identity, so it is excluded from the idempotency argument hash: a
keyed call that reconnects with a different roots state still replays instead of failing
idempotency_conflict, and pre-existing dedup entries keep matching unchanged. Results stored
before this change carry no value and keep omitting the key.RESULT_FORMATstays7(the
field is already known at that format, so no stored payload became unreadable); bumps
FINGERPRINT(schema-63→schema-64) for the publishedcodex://result-metadescription
that now states which run the value describes. Not breaking (#393). -
capabilities-resultschema'srequiredlist contradicted the default response.
ToolCapability.use_when/.returnswere marked required in the published schema (reachable via
codex_capabilities(include_schemas=["capabilities-result"])), butdetail="summary"— the
default — strips both from everytool_detailsentry, so a strict client validating the
default response against its own published schema failed. Both fields are now optional in the
schema, modeling both detail modes; the response bytes are unchanged in either mode. Non-breaking:
the schema is corrected to describe what was already being sent. -
CapabilitiesDetailParam's description and thecodex_capabilitiesdocstring described
async_lifecycleas part of every summary entry; it is only present for the*_asynctools
(3 of 17), which both now say explicitly. -
A
resources/readfailure'serror.datanow carriesresource_uri(the URI that was
requested) andrequest_id, matching the correlation fields the tool-error carrier already
has viameta.request_id. Both are optional and populated only on the JSON-RPC (resource)
carrier — the tool carrier is unaffected, since duplicatingrequest_idthere would be two
homes for one fact (audit F6, #185).resource_error_carrierincodex_capabilitiesis
updated to name both fields and to disclose, as a deliberate divergence, that this server
keepscode/messagerather than themachine_code/human_messagespelling some §6
profiles use. -
The
*_asynctools'JobStartedresult now carries afollow_upobject
({next_step, tool, arguments, alternative}, the same shape aserror.repair) naming
codex_job_statuswith literally callable arguments (audit F7).