openai/codex · error · PermissionProfileResolutionError
permissions profile inheritance cycle detected: {}
Error message
permissions profile inheritance cycle detected: {} What it means
resolve_profile records each profile name it visits while following extends; when the next name already appears in the chain it stops and returns Cycle, with the loop rendered as `a -> b -> a`. Permission inheritance must form a DAG that terminates at a profile without extends; both self-extension and multi-hop loops are rejected before any merging happens.
Source
Thrown at codex-rs/config/src/permissions_toml.rs:139
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum PermissionProfileResolutionError {
#[error("default_permissions refers to undefined profile `{profile_name}`")]
UndefinedProfile { profile_name: String },
#[error(
"permissions profile `{profile_name}` extends undefined profile `{parent_profile_name}`"
)]
UndefinedParent {
profile_name: String,
parent_profile_name: String,
},
#[error(
"permissions profile `{profile_name}` cannot extend unsupported built-in profile `{parent_profile_name}`"
)]
UnsupportedBuiltInParent {
profile_name: String,
parent_profile_name: String,
},
#[error(
"permissions profile inheritance cycle detected: {}",
cycle.join(" -> ")
)]
Cycle { cycle: Vec<String> },
#[error("failed to serialize permissions profile while resolving inheritance: {source}")]
SerializeProfileToml {
#[source]
source: toml::ser::Error,
},
#[error(
"failed to deserialize merged permissions profile while resolving inheritance: {source}"
)]
DeserializeProfileToml {
#[source]
source: toml::de::Error,
},
}
View on GitHub (pinned to 339751715c)
Solutions
- Follow the exact cycle printed in the message and remove or repoint the extends that closes it (usually the last link)
- Factor shared settings into one base profile that others extend — a tree, not a ring
- Re-run profile resolution after each extends edit; cycles are detected before merge, so no partial state is written
Example fix
# before — a -> b -> a
[permissions.a]
extends = "b"
[permissions.b]
extends = "a"
# after — one base, one child
[permissions.base]
network = { enabled = false }
[permissions.a]
extends = "base" Defensive patterns
Strategy: validation
Validate before calling
use std::collections::BTreeMap;
use codex_config::PermissionProfileToml;
// DFS with a visited set over extends edges before calling resolve_profile.
fn find_inheritance_cycle(
entries: &BTreeMap<String, PermissionProfileToml>,
start: &str,
) -> Option<Vec<String>> {
let mut seen: Vec<String> = Vec::new();
let mut name = start.to_string();
loop {
if let Some(pos) = seen.iter().position(|n| n == &name) {
return Some(seen[pos..].to_vec());
}
seen.push(name.clone());
let profile = entries.get(&name)?; // None => chain ends, no cycle
name = profile.extends.clone()?; // None => chain ends, no cycle
}
} Type guard
// True when the profile's own name does not appear in its extends target
// (catches the direct self-loop; use the DFS above for full chains).
fn extends_itself(name: &str, profile: &PermissionProfileToml) -> bool {
profile.extends.as_deref() == Some(name)
} Try / catch
match permissions.resolve_profile(name, builtin_lookup) {
Err(PermissionProfileResolutionError::Cycle { cycle }) => {
// cycle is e.g. ["a", "b", "a"]; remove or repoint the extends that
// closes the loop (usually the last link) and retry.
}
Err(e) => return Err(e.into()),
Ok(profile) => { /* ... */ }
} Prevention
- Model inheritance as a tree: shared settings go in one base profile that others extend
- A profile's extends must never name an ancestor — or itself
- Re-run profile resolution in CI config checks so cycles are caught before deploy
When it happens
Trigger: `[permissions.a] extends = "b"` together with `[permissions.b] extends = "a"`; or a profile whose extends equals its own name. Resolving any profile on the loop fails as soon as the loop closes.
Common situations: Two profiles refactored to share settings and accidentally cross-linked; copy-pasted extends lines; long chains where an edit reintroduces an earlier name; generated configs that splice overrides cyclically.
Related errors
- permissions profile `{profile_name}` extends undefined profi
- default_permissions refers to undefined profile `{profile_na
- permissions profile `{profile_name}` cannot extend unsupport
- remote control URL cannot be a base
- AWS profile must be configured
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/b7b8378fa115183d.
Report an issue: GitHub.