* fix(models): reuse the Claude Code OAuth token read from a file descriptor
ClaudeChatModel accepts a Claude Code OAuth token through
CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR, but that handoff can be drained
only once: a pipe returns EOF after the first read and a file descriptor
keeps its advanced offset. _read_secret_from_file_descriptor read it again
on every call and kept nothing. Every ClaudeChatModel instance loads
credentials in model_post_init, and create_chat_model builds fresh
instances per run, so with a descriptor-only handoff the first model
authenticated and every model after it -- including the title model of
the very first run -- had no credential. The Anthropic SDK then raised
"Could not resolve authentication method" before sending a request.
A secret read from a descriptor is now kept for the life of the process,
keyed by (env_var, fd), so a different descriptor is still read fresh.
The read happens under a lock so two threads building their first model
concurrently cannot race one of them to EOF. Empty reads and OSError are
not cached and behave as before; lookup order, config keys, and log
messages are unchanged.
* docs(changelog): reference #5411 in the Claude Code OAuth descriptor fix entry
* test(models): pin that a closed descriptor handoff keeps its token
Review follow-up on #5411: the descriptor secret cache is keyed on the
fd number, which the OS recycles. Folding os.fstat identity into the key
would break the property the cache exists for -- once the handoff fd is
closed after the first read, fstat raises EBADF and every later model
would lose the token again -- and it would still miss a regular file
rewritten in place, which keeps its st_dev/st_ino.
The handoff is fixed at process start, so keep the number as the key and
state the invariant instead: a closed handoff keeps serving its token, a
secret placed on a recycled number is not re-read, and anything handing
over a new secret in-process must clear the cache. A new test pins the
closed-handoff behavior; an fstat-fingerprinted key fails exactly that
test.
* fix(models): stop reasoning_effort from reaching the constructor twice
The regular lead-agent build forwards reasoning_effort to
create_chat_model even when neither the request nor the custom agent
chose one. The factory spread that kwarg next to the profile settings,
so any profile that also yielded reasoning_effort -- a top-level value,
when_thinking_enabled, when_thinking_disabled, or the minimal effort the
extra_body.thinking disable path injects -- made the constructor raise
"got multiple values for keyword argument 'reasoning_effort'", and the
lead agent could not be built for that model. #2017 moved the
factory-injected value out of kwargs but left the caller-supplied one.
The requested effort now leaves kwargs once and layers like
model_overrides: a non-None value replaces the profile value, None keeps
it, and the thinking transforms applied afterwards still decide the
final value. Codex keeps resolving the requested value itself, so a
level it does not accept and a profile without effort support still
fall back to medium.
* docs(changelog): reference #5403 in the reasoning_effort collision fix entry
* docs: govern agent guidance size
* refactor: split agent guidance by code scope
* Clarify virtual path handling in AGENTS.md
Updated the translation section to clarify the role of `LocalSandboxProvider` and the handling of virtual paths in the tool layer.
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>