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

  1. Follow the exact cycle printed in the message and remove or repoint the extends that closes it (usually the last link)
  2. Factor shared settings into one base profile that others extend — a tree, not a ring
  3. 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

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


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/b7b8378fa115183d. Report an issue: GitHub.