Allow specifying specific commands that don't need permission to run

Resolved 💬 24 comments Opened Sep 3, 2025 by rben01 Closed Jan 14, 2026
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

What feature would you like to see?

In .claude/settings, users can set explicitly allows commands for Claude to run so that it doesn't need permission. e.g.

{
	"permissions": {
		"allow": [
			"Bash(mkdir:*)",
			"Bash(rg:*)",
			"Bash(npm run build:*)",
			"Bash(grep:*)",
			"Bash(npm run format:*)"
			// ...
		]
	}
}

It's great that Claude Code lets me tell it which tools it doesn't need to ask for permission to use. It would be great if Codex CLI has a similar whitelist of user-specific permissions (not necessarily with the above syntax) so that it would stop repeatedly pestering me to ask if it can run my tests.

Are you interested in implementing this feature?

_No response_

Additional information

_No response_

View original on GitHub ↗

24 Comments

michaelboeding · 10 months ago

Is this on the roadmap still? Would be very useful

NightMachinery · 9 months ago

I also need this. Codex keeps asking me to approve each curl call.

zwalden · 9 months ago

Curious about this too.

fcakyon · 9 months ago

Is this still not available in Codex? 😩

SwiftedMind · 9 months ago

This would help me a lot because I can't run xcodebuild inside the sandbox

xshapira · 9 months ago

We all need this one.

mattcorey · 9 months ago

This enhancement is probably my #1 request - there are certain commands that I always want to allow, such as running the scripts to build and test my project (similar to the xcodebuild example from @SwiftedMind a few weeks ago).

The wildcards are an important aspect to this - tools such as 'axe' (https://github.com/cameroncooke/AXe) would commonly be run over and over again, and provide different parameters each time. Needing to approve each individual combination of parameter is a non-starter, but running on yolo mode is _not_ what I want to do.

l3wi · 8 months ago

+1 to have this feature. Its so tedious to use codex otherwise

berndeplo · 8 months ago

+1

ssimeth · 8 months ago

+1

flamerged · 8 months ago

Why the fuck is there an option to allow a command and then not be asked for permission again, but it simply does not work??

tarmolehtpuu · 8 months ago
Why the fuck is there an option to allow a command and then not be asked for permission again, but it simply does not work??

Yeah also got surprised by that, but I think it whitelists even the args - so it will only work if the cmd has exactly the same args.

Came here looking for a way to autoallow gofmt but I am ok reviewing other edits and such. Please consider this feature :)

praxder · 7 months ago

I got really tired of having to always approve this, so I wrote a custom script that allows you to whitelist certain commands. I've been using it for about a month and it's a game changer with Codex CLI. Keep in mind this is my personal script, so you might have to tweak it to get it working right for you, but here's how to set it up:

Prerequisites:

  • Be on a mac
  • Use iTerm2
  • Have Dart installed
  1. Write a zsh alias for when codex starts (put this in ~/.zshrc):
alias code='echo "Starting Codex...." && codex && echo "Ending Codex...."'

this gives us a string to watch for when codex starts and ends.

  1. Put the following script in a file named codex_coprocess.dart (to be used in next step).

You can obviously edit the approvedCommands list as needed.

// ignore_for_file: prefer_interpolation_to_compose_strings
import 'dart:convert';
import 'dart:io';

void main() {
  showNotification(title: 'Starting', text: 'Starting Codex Coprocess');

  ///////////////
  // VARIABLES //
  ///////////////
  const bufferMaxLines = 20;
  final approvedCommands = [
    'cd',
    'curl',
    'dart',
    'echo',
    'export',
    'flutter',
    'gh pr diff',
    'gh pr review',
    'gh pr status',
    'gh pr view',
    'git checkout',
    'git fetch',
    'grep',
    'melos',
    'tail',
    'tee',
    'xcodebuild',
    'xcrun',
  ];
  final actions = [
    (
      RegExp(
        r'Would you like to run the following command\?\n?.*',
        multiLine: true,
        dotAll: true,
      ),
      (final String match) {
        final commandLine = extractCommandLine(match);
	if (commandLine == null) return;

        if (areAllCommandsApproved(commandLine, approvedCommands)) {
          stdout.write('2');
        } else {
          showNotification(
            title: "Pending Request",
            text: 'Tool Needs Approval',
            playSound: true,
          );
        }
      },
    ),
  ];
  ///////////////////
  // END VARIABLES //
  ///////////////////

  final buffer = StringBuffer();
  final ansiRegex = RegExp(
    r'(?:\x9B|\x1B\[)[0-?]*[ -/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|\x1B[PXT^_].*?\x1B\\|\x1B[@-Z\\^_]|\x1B[ -/]*[@-~]',
    dotAll: true,
  );
  final controlCharacterRegex = RegExp(
    r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]',
  );

  stdin.transform(utf8.decoder).listen((final input) {
    if (input.contains('Ending Codex....')) {
      showNotification(title: 'Done', text: 'Ended Codex Coprocess');
      exit(0);
    }

    final cleanInput = input
        .replaceAll(ansiRegex, '')
        .replaceAll(controlCharacterRegex, '');
    buffer.write(cleanInput);

    final numLines = buffer.toString().split('\n').length - 1;
    if (numLines > bufferMaxLines) {
      final bufferWindow = buffer
          .toString()
          .split('\n')
          .sublist(numLines - bufferMaxLines)
          .join('\n');
      buffer
        ..clear()
        ..write(bufferWindow);
    }

    for (final (regexp, action) in actions) {
      final match = regexp.stringMatch(buffer.toString());
      if (match != null) {
        action(match);
        buffer.clear();
        break;
      }
    }
  });
}

/////////////////////////////
// Command Approval Helpers //
/////////////////////////////

/// Extracts the command line from a Codex approval prompt match
String? extractCommandLine(String match) {
  final commandRegex = RegExp(r'\$\s+(.+)$', multiLine: true);
  final commandMatch = commandRegex.firstMatch(match);
  return commandMatch?.group(1)?.trim();
}

/// Splits a command line into individual commands by &&, ;, and |
List<String> splitCommands(String commandLine) {
  // Split on &&, ;, or | and trim each segment
  return commandLine
      .split(RegExp(r'&&|;|\|'))
      .map((segment) => segment.trim())
      .where((segment) => segment.isNotEmpty)
      .toList();
}

/// Extracts the base command from a segment, removing environment variables
String extractBaseCommand(String segment) {
  // Remove environment variable assignments (KEY=VALUE) from the start
  final envVarRegex = RegExp(r'^(\w+=\S+\s+)+');
  final cleaned = segment.replaceFirst(envVarRegex, '');
  return cleaned.trim();
}

/// Checks if a command is approved using prefix matching
bool isCommandApproved(String command, List<String> approvedCommands) {
  return approvedCommands.any((approved) => command.startsWith(approved));
}

/// Checks if all commands in a chain are approved
bool areAllCommandsApproved(String commandLine, List<String> approvedCommands) {
  final commands = splitCommands(commandLine);

  for (final segment in commands) {
    final baseCommand = extractBaseCommand(segment);
    if (!isCommandApproved(baseCommand, approvedCommands)) {
      return false;
    }
  }

  return true;
}

void showNotification({
  required final String title,
  required final String text,
  final bool playSound = false,
}) {
  Process.runSync('osascript', [
    '-e',
    'display notification "$text" with title "$title"',
  ]);
  if (playSound) {
    Process.runSync('afplay', ['/System/Library/Sounds/Funk.aiff']);
  }
}

/////////////////////////
// Debugging Utilities //
/////////////////////////
void log(final String log, {final bool shouldClear = false}) {
  final file = File('/Users/adam/Desktop/log.txt');
  final currentContents = file.existsSync() && !shouldClear
      ? file.readAsStringSync()
      : '';
  file.writeAsStringSync(currentContents + log);
}

  1. Create an iTerm2 Coprocess that starts up the custom script when it sees the text "Starting Codex...."

<img width="1338" height="684" alt="Image" src="https://github.com/user-attachments/assets/ea4fe1d8-3a21-4547-b589-70f4cf0a519a" />

<img width="1338" height="684" alt="Image" src="https://github.com/user-attachments/assets/7965cab2-6a41-47bc-9a8f-dca8b46fdf83" />

And the full text for the second screenshot, second part is this (you'll need to update this for your system).

/Users/adam/Developer/flutter_sdk/bin/dart "/Users/adam/Developer/Scripts/codex_coprocess/lib/codex_coprocess.dart"

Obviously you will need to update the paths to be relative to your machine.

It should now be working and when an approvedCommand is requested by codex, it should automatically be approved and continue. This has been a game changer for me.

rimesime-alt · 7 months ago

+1

amit777 · 7 months ago

+1

hivokas · 7 months ago

Also had this concern, was able to fix it by adding

prefix_rule(pattern=["eslint"], decision="allow")

to ~/.codex/rules/default.rules.

Now Codex can run eslint with any arguments without asking me permission.

johan456789 · 6 months ago
Also had this concern, was able to fix it by adding `` prefix_rule(pattern=["eslint"], decision="allow") ` to ~/.codex/rules/default.rules`.

this works quite well. documentation:

https://developers.openai.com/codex/local-config/#rules-preview

Culpable · 6 months ago

prefix_rule works for commands with separate argument tokens (e.g. ["eslint"] allows eslint <any args>), but fails when Codex wraps commands as ["/bin/zsh", "-lc", "<entire command>"].

The third token contains the full heredoc, so there's no way to match just the prefix.

Example that works:

# ["eslint", "src/file.js"] — token 0 matches, remaining tokens allowed
prefix_rule(pattern=["eslint"], decision="allow")

Example that fails:

# ["/bin/zsh", "-lc", "cd /path && npx tsx <<'EOF'\n...\nEOF"]
# Token 2 is the ENTIRE command string including heredoc — no prefix match possible
prefix_rule(pattern=["/bin/zsh", "-lc", "cd /path && npx tsx"], decision="allow")

Request: Please add substring/glob support for within-token matching, e.g.:

prefix_rule(
    pattern=["/bin/zsh", "-lc", startswith("cd /path && npx tsx")],
    decision="allow"
)
etraut-openai contributor · 6 months ago

This feature is supported through the "rules" mechanism, which is documented here.

fschwahn · 6 months ago

@etraut-openai

Request: Please add substring/glob support for within-token matching

This does not seem to be possible with the rules mechanism at the moment.

etraut-openai contributor · 6 months ago

@fschwahn, we're looking at the next round of extensions to the existing Rules feature, and we're interested in ideas and feedback. I'd appreciate it if you'd create a new enhancement request and clarify what changes or additions you'd like to see. This thread is quite long (and old) and covers a lot of things that are already supported.

fschwahn · 6 months ago

@Culpable you want to open your comment as a new issue? I think it's very clearly written.

aa403 · 5 months ago

this should be reopened -- @etraut-openai the current method doesn't handle wildcards at all as far as i can see. it's super frustrating.

etraut-openai contributor · 5 months ago

@aa403, if you'd like to suggest a new capability, please open a new feature request.