0.149.0 rejects symlinked custom-agent role files with misleading "agent type is currently not available"
What version of Codex is running?
- Failing CLI/app-server:
codex-cli 0.149.0 - Working control:
codex-cli 0.148.0
What platform and surface are affected?
- Remote Linux development host
- Interactive CLI and an unmanaged remote app-server
- The Desktop app itself was not downgraded
- ChatGPT-authenticated Codex session
- The installed custom-agent configuration was unchanged between the two tests
Summary
Codex 0.149.0 rejects configured custom-agent role files installed as symbolic links and returns only:
agent type is currently not available
The same symlink-based installation works after downgrading only the remote CLI/app-server to 0.148.0 and restarting it.
The affected installation intentionally links the individual role files from a version-controlled configuration repository into ~/.codex/agents/. All affected roles are symlinks, including implementer, code-mapper, and the other custom profiles.
Example sanitized layout:
~/.codex/agents/implementer.toml -> <configuration-repository>/codex-home/agents/implementer.toml
~/.codex/agents/code-mapper.toml -> <configuration-repository>/codex-home/agents/code-mapper.toml
No role contents, account secrets, or private repository paths are included here.
Likely root cause
This now appears directly related to #39299, merged into the 0.149.0 release:
- PR: https://github.com/openai/codex/pull/39299
- Commit: https://github.com/openai/codex/commit/1a6e07a4febcc0ecfa04464f5e95cb47144cd746
That change explicitly says it will "reject symlinked user role files". It also changed custom-role reading from ordinary tokio::fs::read_to_string() to read_sensitive_file_to_string() and added the test:
apply_role_rejects_symlinked_role_file
The test expects the public error:
agent type is currently not available
This matches the observed 0.149.0 behavior exactly. The symlink restriction may be an intentional security boundary, but it creates an undocumented compatibility break for installations that previously managed custom-agent files through symlinks. The generic error also hides the actionable cause.
A second, distinct 0.149.0 custom-role regression associated with the bounded-role work is tracked in #40042:
#40042 concerns model_instructions_file being ignored and is not a duplicate of this report, but both appear related to the role-loading changes introduced by #39299.
Steps to reproduce
- Create a valid custom-agent role file outside
~/.codex/agents/. - Symlink it into the normal custom-agent location:
``sh``
ln -s /absolute/path/to/implementer.toml ~/.codex/agents/implementer.toml
- Register/use that role through the normal Codex custom-agent configuration.
- Start a fresh Codex CLI or app-server-backed task on 0.149.0.
- Attempt to spawn it:
``text``
agent_type = "implementer"
fork_turns = "none"
- Observe:
``text``
agent type is currently not available
- Substitute Codex CLI/app-server 0.148.0 without changing the role symlink or configuration.
- Fully restart the app-server and start a fresh task.
- Repeat the same read-only spawn probe.
Expected behavior
At minimum, Codex should return an actionable error explaining that symlinked custom-agent role files are not permitted.
Preferably, one of these contracts should be explicit:
- Safely allow role symlinks whose resolved targets satisfy the intended trust boundary; or
- Document that custom-agent role files must be physical files and reject symlinks during configuration loading with a clear path-safe warning.
The current behavior makes a discovered/configured role look generically unavailable and gives no indication that copying the same TOML into place may resolve it.
Actual behavior
In 0.149.0, the configured role is rejected before the child starts:
agent type is currently not available
The underlying symlink rejection is only visible in source/tests, not in the user-facing error.
Controlled A/B evidence
| Fresh task | cli_version recorded in rollout | Result |
| --- | --- | --- |
| 01a02a68-2edd-7d91-987b-acfd76e53f90 | 0.149.0 | Four symlink-installed configured-role spawn attempts were rejected with the exact error above |
| 01a02a6c-46a7-7101-9dc3-ba7f407ffb83 | 0.148.0 | Successfully spawned custom implementer, custom implementation_lead, and a nested custom implementer using fork_turns = "none" |
| 01a0250f-f83e-70e3-b2ee-f8a312e05999 | 0.148.0 | Historical successful custom-role workflow using the same installation style |
The 0.148.0 preflight was run only after both the CLI and the restarted remote app-server reported 0.148.0.
Sanitized rollout evidence:
fresh task 01a02a68-2edd-7d91-987b-acfd76e53f90
cli_version: 0.149.0
spawn result: agent type is currently not available
fresh task 01a02a6c-46a7-7101-9dc3-ba7f407ffb83
cli_version: 0.148.0
custom implementer: started
custom implementation_lead: started
nested custom implementer with fork_turns="none": started
All probes were read-only. No repository, release, configuration, or CODEX_HOME files were changed during the probes.
Related, but distinct reports
- #26408 has the same public error text, but concerns project-scoped
.codex/agentsdiscovery on 0.137.0. - #20077 concerns full-history fork/override incompatibility. The successful nested control explicitly used
fork_turns = "none". - #26868 concerns a child being created without its profile being applied. Here, 0.149.0 rejects the role before a child starts.
- #40042 concerns a different bounded-role regression where
model_instructions_fileis silently ignored.
Suggested resolution
- Surface a specific privacy-safe error such as:
``text``
custom agent role file must be a regular file; symbolic links are not supported
- Document the symlink restriction and the supported installation method in the custom-agent documentation.
- Consider allowing symlinks after resolving and validating the target against an explicit trust boundary.
- Add release-note coverage for the 0.149.0 compatibility change.
- Keep a regression test for both the security rule and the user-facing diagnostic.
1 Comment
I bisected the 0.148.0 → 0.149.0 delta for this code path. The change that matches your A/B is #39299 (
1a6e07a4fe, "Restrict agent roles to bounded configuration overrides"), which landed in 0.149.0.What changed
load_role_layer_tomlincodex-rs/core/src/agent/role.rsswapped the reader used for user-defined role files:read_sensitive_file_to_string(added in #39200,codex-rs/exec-server/src/regular_file.rs:21) opens withO_NOFOLLOWand then rejects anything whose metadata says symlink. Its doc comment states the intent directly: "Reads a regular UTF-8 file without following a symlink at its final path component." The PR body lists it too — "reject symlinked user role files" — so the rejection itself is deliberate, androle_tests.rs:109pins it.The old reader followed symlinks. So a role file that is a symlink loaded fine on 0.148.0 and is refused on 0.149.0. Confirming the syscall-level difference on Linux:
| role file | 0.148.0 (
read_to_string) | 0.149.0 (O_NOFOLLOW) || --- | --- | --- |
|
implementer.toml→ symlink | ok, 80 bytes |ELOOP||
code_mapper.tomlregular file | ok, 80 bytes | ok |Only the final path component matters. A symlinked
~/.codex/agents/directory still resolves; a symlinked*.tomlinside it does not.This also explains why only configured roles break. Built-in roles take the other branch in the same function and come from
include_str!(role.rs:419), so they never touch the filesystem and never hit the new reader.Why the error text gives you nothing
apply_role_to_configcollapses every failure fromapply_role_to_config_innerinto one string, and the real cause only reaches atracing::warn!:A missing role file, a symlinked role file, and a role file with malformed TOML all surface as
agent type is currently not available. Three tests inrole_tests.rsassert exactly that, so the masking is locked in. That is the part I would call the actual defect here — the rejection may be intended, but it is currently undiagnosable from the client side.Confirming it on your host
Anything listed there is refused on 0.149.0 and was accepted on 0.148.0. Dotfile managers that link role files into place (stow, chezmoi, a hand-rolled
ln -s) produce exactly this layout, which fits a remote dev host.If that command prints nothing, the cause is one of the other
?sites in the rewrittenapply_role_to_config_innerrather than the reader. The warn line separates them:I can run that on my side too if it would help narrow things down, though my roles are plain files so I would be reproducing your layout rather than my own.
Notes for a fix
Surfacing the underlying error to the caller would have turned this into a self-service diagnosis. Beyond that, the release notes for 0.149.0 list #39299 only in the raw commit list, not in the summarized sections, so a role layout that worked stopped working with no signal.
CHANGELOG.mdpoints at the releases page and carries nothing about it either.Whether legitimate symlinked role files should keep working is a policy call I can't make from outside. The hardening blocks a real attack — the test target in
regular_file_tests.rswritesmodel_provider = 'attacker'— but resolving the link and validating the target's ownership and location would preserve dotfile setups while keeping that closed.Environment for the checks above: Linux, repo at
83d1fe0e67, tagsrust-v0.148.0andrust-v0.149.0. All read-only; I did not build or run codex itself.