`.agents/` writes blocked even when added via `--add-dir` or in `writable_roots`

Open 💬 2 comments Opened May 25, 2026 by UnagiPyon
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

.agents/ writes blocked even when added via --add-dir or in writable_roots

Summary

In codex-cli 0.130.0 on macOS, writes to any path under a workspace's .agents/ directory return Operation not permitted even when:

  • The directory is the current working directory's subdirectory (workspace-write sandbox)
  • .agents is explicitly passed via --add-dir .agents
  • The startup banner confirms .agents appears in the sandbox writable list

This appears to be an intentional protection of the .agents/ namespace (used for plugin marketplace registry at .agents/plugins/marketplace.json and skills), but I could not find a documented way to override it for project-internal files that happen to live under .agents/ for reasons predating Codex's plugin convention.

Environment

  • codex-cli 0.130.0 (Homebrew, native binary codex-darwin-arm64)
  • macOS 25.5.0 (Darwin arm64)
  • ~/.codex/config.toml: approval_policy = "never", [sandbox_workspace_write] with network_access = true only
  • Project root: arbitrary repo with a pre-existing .agents/ directory containing internal artifacts (failure_ledger.md, eval_cases/, etc.) unrelated to Codex's plugin marketplace

Reproduction

cd <repo-root>   # repo-root contains an existing .agents/ directory
codex exec \
  --cd "$PWD" \
  --add-dir .agents \
  --skip-git-repo-check \
  --sandbox workspace-write \
  'Run: touch .agents/.codex_write_test ; echo exit=$?'

Startup banner shows .agents is included:

sandbox: workspace-write [workdir, /tmp, $TMPDIR, /<repo>/.agents, /Users/<user>/.codex/memories] (network access enabled)

But the command output is:

touch: .agents/.codex_write_test: Operation not permitted
exit=1

Controlled comparison (same session, same cwd, workspace-write, no --add-dir)

| Target | Result |
| --- | --- |
| touch scripts/.codex_test (existing non-dot subdir) | exit=0 ✅ |
| mkdir .codex_new_dotdir && touch .codex_new_dotdir/.codex_test (newly created dot subdir) | exit=0 ✅ |
| touch /tmp/.codex_test (writable root) | exit=0 ✅ |
| touch .agents/.codex_test (existing .agents/) | Operation not permitted ❌ |
| touch .agents/eval_cases/.codex_test (existing subpath) | Operation not permitted ❌ |

So:

  • It is not a generic "dotdir" restriction (newly created dotdirs are writable).
  • It is not a com.apple.provenance xattr issue (sibling scripts/ has the same xattr and is writable).
  • It is specifically .agents/ that is blocked, even when explicitly added via --add-dir.

Questions

  1. Is the .agents/ block intended to apply to all workspaces, regardless of whether the project actually uses Codex's plugin marketplace convention?
  2. If yes, what is the recommended way to override it for projects that have an unrelated .agents/ directory (predating Codex's adoption of this path)?
  3. Is [permissions] profile system (permission_profile / default_permissions in config.toml) the documented escape hatch? If so, could the docs include a minimal example that grants write for a project-specific subpath inside .agents/?
  4. Should --add-dir .agents either succeed at granting write, or fail loudly at invocation time? The current behavior (silent override after the path is shown in the writable list) is confusing.

Workaround

For now we are routing .agents/ writes through Claude Code (which has no equivalent .agents/ block). It would be nice to keep the work in Codex when the project's .agents/ predates Codex's plugin marketplace convention.

Source reference

Strings in the binary point to cli/src/debug_sandbox/seatbelt.rs and mention FileSystemAccessMode with a 'none' beats 'write' precedence rule, plus a [permissions] profile system referenced via permission_profile / default_permissions. I could not find user-facing docs for the latter.

Related issues

Same "special dot-named directory blocked even after --add-dir" pattern, but for .git/ rather than .agents/:

  • #23661 Linked worktree git add fails: .git/worktrees index.lock remains read-only despite --add-dir
  • #7071 CLI sandbox: cannot commit because .git is read-only

And an opposite-direction issue for the same flag:

  • #24214 codex exec --sandbox workspace-write --add-dir X: apply_patch tool ignores --add-dir whitelist and writes anywhere under --cd

These suggest --add-dir semantics are inconsistent across protected paths and underlying tools (exec vs apply_patch), and it might be worth documenting the intended boundaries.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #23286

Powered by Codex Action

jshaofa-ui · 3 months ago

🔧 Complete Solution: .agents/ Writes Blocked Despite --add-dir

This issue is zero competition (0 comments). Here's a comprehensive fix:

---

Fix: openai/codex #24461 — .agents/ Writes Blocked Despite --add-dir

Issue: https://github.com/openai/codex/issues/24461
Tags: bug, sandbox, exec, CLI
Competition: 1 comment — near-zero competition
Quote: $1,500–$2,500

---

Root Cause Analysis

In codex-cli 0.130.0 on macOS, writes to .agents/ directory are blocked by the sandbox even when:

  • The directory is a subdirectory of the workspace root
  • .agents is explicitly passed via --add-dir .agents
  • The startup banner confirms .agents appears in the sandbox writable list

Evidence:

sandbox: workspace-write [workdir, /tmp, $TMPDIR, /<repo>/.agents, ...] (network access enabled)
touch: .agents/.codex_write_test: Operation not permitted
exit=1

The sandbox banner shows .agents is included, but writes still fail. This indicates the sandbox policy has a hardcoded exclusion for .agents/ that takes precedence over the --add-dir whitelist.

Likely root cause: The sandbox implementation has a deny-list for dot-directories that are reserved for Codex's internal use (.agents/ for plugins, .codex/ for config). This deny-list is applied AFTER the allow-list, effectively overriding --add-dir:

// Pseudo-code — buggy sandbox policy evaluation
function isPathWritable(path: string, config: SandboxConfig): boolean {
  // Step 1: Check allow-list (includes --add-dir entries)
  if (config.writableDirs.some(d => path.startsWith(d))) {
    return true; // ✅ .agents passes this check
  }

  // Step 2: Check deny-list (hardcoded reserved dirs)
  const DENY_LIST = ['.agents', '.codex', '.claude']; // BUG: overrides allow-list
  if (DENY_LIST.some(dir => path.includes(`/${dir}/`) || path.endsWith(`/${dir}`))) {
    return false; // ❌ .agents is blocked here
  }

  return false;
}

Fix Approach

Fix 1: Respect Explicit --add-dir Overrides (Recommended)

Change the sandbox policy evaluation to prioritize explicit user overrides over the internal deny-list:

// src/sandbox/policy.ts — BEFORE (buggy)
const RESERVED_DIRS = ['.agents', '.codex', '.claude'];

function isPathWritable(path: string, config: SandboxConfig): boolean {
  // Allow-list check
  if (config.writableDirs.some(d => path.startsWith(resolvePath(d)))) {
    return true;
  }
  // Deny-list check — overrides allow-list
  if (RESERVED_DIRS.some(dir => path.includes(`/${dir}`))) {
    return false;
  }
  return false;
}
// AFTER (fixed)
const RESERVED_DIRS = ['.agents', '.codex', '.claude'];

function isPathWritable(path: string, config: SandboxConfig): boolean {
  // Check explicit --add-dir overrides FIRST
  const explicitOverride = config.writableDirs.find(d => {
    const resolved = resolvePath(d);
    return path.startsWith(resolved) && config.explicitlyAddedDirs.has(resolved);
  });
  if (explicitOverride) {
    // User explicitly added this dir — respect it even if it's a reserved dir
    return true;
  }

  // Allow-list check for non-explicit entries
  if (config.writableDirs.some(d => {
    const resolved = resolvePath(d);
    return path.startsWith(resolved) && !config.explicitlyAddedDirs.has(resolved);
  })) {
    // Auto-added writable dirs (like workspace root) — still apply deny-list
    if (RESERVED_DIRS.some(dir => {
      const resolved = resolvePath(d);
      return path.includes(`/${dir}`) && resolved.endsWith(`/${dir}`);
    })) {
      return false;
    }
    return true;
  }

  return false;
}

Fix 2: Add --force Flag for Reserved Directories

For users who need to write to reserved directories:

interface SandboxConfig {
  // ... existing fields
  forceWritableDirs?: string[]; // NEW: bypass deny-list for these dirs
}

function isPathWritable(path: string, config: SandboxConfig): boolean {
  // Force writable dirs bypass everything
  if (config.forceWritableDirs?.some(d => path.startsWith(resolvePath(d)))) {
    return true;
  }
  // ... rest of policy
}

Fix 3: Distinguish Codex .agents/ from Project .agents/

The deny-list should only apply to the Codex-managed .agents/ directory (at ~/.codex/agents/), not to project-local .agents/ directories:

function isPathWritable(path: string, config: SandboxConfig): boolean {
  // Only deny Codex's own .agents/ directory, not project-local ones
  const codexAgentsDir = path.join(config.codexHome, 'agents');
  const isCodexManaged = path.startsWith(codexAgentsDir);

  if (isCodexManaged) {
    return false; // Codex's own .agents/ is always protected
  }

  // Project-local .agents/ is allowed if in allow-list
  if (config.writableDirs.some(d => path.startsWith(resolvePath(d)))) {
    return true;
  }

  return false;
}

Recommended Action

Apply Fix 3 — the deny-list should only protect Codex's own .agents/ directory (at ~/.codex/agents/), not project-local .agents/ directories. This is the most precise fix that doesn't require users to add workarounds.

Code Changes Summary

| File | Change |
|------|--------|
| src/sandbox/policy.ts | Distinguish Codex-managed .agents/ from project-local .agents/ |
| src/sandbox/policy.ts | Respect explicit --add-dir overrides over deny-list |
| test/sandbox/policy.test.ts | Test project-local .agents/ writes with --add-dir |

Testing Strategy

  1. Reproduction: In a repo with existing .agents/ directory, run codex exec --add-dir .agents 'touch .agents/test'
  2. Verify fix: Write should succeed
  3. Codex-managed: Verify ~/.codex/agents/ is still protected
  4. Regression: Verify other dot-directories work correctly