An install-repair and hardening release. The headline is the dependency cap: fastmcp 4.0.0
published and every earlier release's uncapped fastmcp>=3.4 floor now resolves it and fails at
import, so fresh installs — including the .mcp.json plugin path — are broken until this release
supersedes them. Alongside the cap, the three free diagnostics gain the same envelope guarantees
as the paid tools, and the developer_instructions docs stop overstating what the parameter and
its fingerprint prove. The result fingerprint moves schema-85 → schema-87 (both hops
additive or wording-only); nothing is breaking. The fastmcp 4 port itself lands separately
(#570).
Fixed
-
Fresh installs no longer resolve the incompatible fastmcp 4 (#572): every released version
declaresfastmcp>=3.4with no upper bound, so once fastmcp 4.0.0 (on mcp 2.1.1) published, a
fresh install resolved it and failed at import (cannot import name 'McpError' from 'mcp') —
including the.mcp.jsonplugin path. The floor is now capped at<4. Already-published
versions cannot be repaired, only superseded by releasing this cap. The fastmcp 4 port itself is
#570. -
The three free diagnostics can no longer fail as a raw MCP protocol error (#541):
codex_status,codex_capabilitiesandcodex_modelsare synchronous, so the_guard
decorator — which wraps async tools — never covered them, and anything they raised escaped the
handler instead of returning the documented envelope. That was worst forcodex_status, whose
entire job is to answer "is codex usable right now?": the tool an agent reaches for because
something is already wrong was the one least able to report it. All three are now guarded and
advertiseinternal_errorin their per-toolerror_codes; their output schemas already carried
a success|error union, so no schema shape changed. The resultfingerprintmovesschema-86->
schema-87. Adding an error code is backward-compatible, so this is not breaking. -
A
codexonPATHthat cannot be executed is a readiness fact, not a crash (#541):
binpath.codex_bin()deliberately falls back to the bare literal"codex"when resolution finds
nothing, andsubprocessthen does its ownPATHlookup without theis_file()+X_OK
predicatebinresolveapplies to its own candidates. An executable directory namedcodex(or a
file with the execute bit set that the kernel cannot exec) therefore raisedPermissionError/
OSError(ENOEXEC)straight out of the probes. Those spawn failures are now classified the way a
missing binary already was, socodex_statusreportscodex_found: falseinstead of raising. -
The three free tools now report their own pinned posture on an invalid-argument error
(#541):READMEdocuments that every shipped tool pins its own tier and sandbox and ignores
CODEX_IN_CLAUDE_TIER_DEFAULT/CODEX_IN_CLAUDE_SANDBOX_DEFAULT. Because the three unguarded
tools had no entry in the posture map, theirinvalid_argumentsenvelope fell back to
config.defaults()and reported whatever an operator had configured — an operator running
TIER_DEFAULT=proposesawcodex_capabilitiesclaimpropose/workspace-write. Guarding them
registers their real posture and restores the documented promise.metavalues are not part of
the discovered surface, so this moves no fingerprint of its own.
Changed
developer_instructionsnow documents that compliance is best-effort (#563): the only
"instructed, not compelled" statement on the surface was scoped to verdicts, which reads as a
promise that everything else applies. It does not — Codex may honor the caller text in full, in
part, or not at all, and no result field attests which happened —meta's fingerprint attests
the request the server accepted and staged, never what the model did with it. Non-compliance may be silent,
though the server's framing does instruct Codex to disclose a conflict with the rules above it,
anddetail="full"returns the raw model text. Stated once in the parameter contract (inline
summary andcodex://paramsfull text), and echoed inREADME.md,COMPATIBILITY.md, and the
bundled skill. The resultfingerprintmovesschema-85->schema-86; documenting an
existing limit weakens no guarantee, so this is not breaking.- The bundled skill no longer overstates what the fingerprint proves (#564): the
options-and-errors reference called it "how you tell a steered run from a default one". It
separates a steered request — it attests what the server SENT, not what the model did — and
its absence means only that the per-call parameter was omitted, not that the run was otherwise
unsteered. The same reference now also states what validation does and does not cost: a value
the server REFUSES costs nothing — unsafe text, the byte cap, framing markers, and the combined
input budget are all rejected pre-spend — while a value it ACCEPTS goes straight into the paid
run, so there is no free positive check. Because the checks stop at the first failure in
unsafe -> byte cap -> marker order, a value that is both over-cap and marker-bearing reports
only the cap.