
Codex
- 663 installs
- 84 repo stars
- Updated August 4, 2026
- oil-oil/codex
codex is a Claude Code skill that delegates clearly framed coding tasks to Codex CLI via a bundled script for developers who explicitly ask Codex to execute autonomous file and bash operations.
About
codex is an Agent Skill from oil-oil/codex that delegates coding tasks to Codex CLI only when the user explicitly requests Codex execution—for example "ask codex to" or equivalent phrasing. Codex runs as an autonomous coding agent with the same tools as Claude including file read/write, grep, and bash, exploring the codebase and implementing changes independently. The invoking agent's role is to understand the problem and frame it clearly before delegation. The skill requires using the bundled shell script rather than calling the codex CLI directly, ensuring consistent output handling across runs.
- Invoke only when the user explicitly requests Codex (e.g. ask codex to, 用 codex 来做)
- Bundled shell script handles output capture, session tracking, and progress streaming—not raw codex CLI
- Task prompts stay under ~500 words: goals and constraints, not step-by-step file lists
- Quote paths with brackets, spaces, or glob-sensitive characters for zsh safety
- Single run per task: exit 0 means read the output file; do not re-run for short silent diffs
Codex by the numbers
- 663 all-time installs (skills.sh)
- +4 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,476 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oil-oil/codex --skill codexAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 663 |
|---|---|
| repo stars | ★ 84 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | oil-oil/codex ↗ |
How do you delegate tasks to Codex CLI?
Delegate a clearly framed coding task to Codex CLI via the bundled script when the user explicitly asks Codex to execute—not for every general coding request.
Who is it for?
Developers using Claude Code who explicitly want Codex CLI to autonomously implement a well-framed coding task in the current repository.
Skip if: General coding requests where the user did not ask for Codex, or workflows requiring the primary agent to implement directly without delegation.
When should I use this skill?
The user explicitly asks to use Codex—phrases like "ask codex to", "let codex execute", or direct Codex delegation requests in any language.
What you get
Codex CLI execution output, implemented code changes, and autonomous codebase exploration results via the bundled delegation script.
- Codex execution output
- Implemented code changes
Files
Critical rules
- Use the bundled shell script rather than calling
codexCLI directly — the script handles output capture, session tracking, and real-time progress streaming correctly. - Run the script once per task. If it succeeds (exit code 0), read the output file and proceed. Don't re-run just because the output seems short — Codex often makes changes quietly without narrating every step.
- Quote file paths containing
[,], spaces, or special characters (e.g.--file "src/app/[locale]/page.tsx"). Without quotes, zsh treats[...]as a glob pattern and fails with "no matches found". - Keep the task prompt to the goal and constraints, not the implementation steps. Aim for under ~500 words. Codex has the same tools as Claude and will explore the codebase itself — spelling out every file to change or every step tends to constrain it rather than help.
- Don't paste file contents into the prompt. Use
--fileto point Codex to key files — it reads them directly at their current version. Pasting contents wastes tokens and risks passing stale code. - Don't mention this skill or its configuration in the prompt. Codex doesn't need to know about it.
How to call the script
Linux/macOS (bash)
The script path is:
~/.claude/skills/codex/scripts/ask_codex.shMinimal invocation:
~/.claude/skills/codex/scripts/ask_codex.sh "Your request in natural language"With file context:
~/.claude/skills/codex/scripts/ask_codex.sh "Refactor these components to use the new API" \
--file src/components/UserList.tsx \
--file src/components/UserDetail.tsxMulti-turn conversation (continue a previous session):
~/.claude/skills/codex/scripts/ask_codex.sh "Also add retry logic with exponential backoff" \
--session <session_id from previous run>Windows (PowerShell)
The script path is:
~/.claude/skills/codex/scripts/ask_codex.ps1Minimal invocation:
& ~/.claude/skills/codex/scripts/ask_codex.ps1 "Your request in natural language"With file context:
& ~/.claude/skills/codex/scripts/ask_codex.ps1 "Refactor these components to use the new API" `
-f src/components/UserList.tsx `
-f src/components/UserDetail.tsxMulti-turn conversation (continue a previous session):
& ~/.claude/skills/codex/scripts/ask_codex.ps1 "Also add retry logic with exponential backoff" `
-Session <session_id from previous run>Output format
The script prints on success:
session_id=<thread_id>
output_path=<path to markdown file>Read the file at output_path to get CodeX's response. Save session_id if you plan follow-up calls.
Workflow
1. Understand the problem: read the key files to grasp what's broken or needed. Focus on being able to describe the problem and goal clearly — you don't need to design the full solution or enumerate every affected file. Codex will explore the codebase itself. 2. Run the script with a focused task description: the goal, key constraints, and any non-obvious context. For discussion or analysis without changes, use --read-only. 3. Pass 1-4 entry-point files with --file as starting hints. Codex has the same tools as Claude and will discover related files on its own — no need to enumerate everything upfront. 4. Read the output — Codex executes changes and reports what it did. 5. Review the changes in your workspace.
For multi-step projects, use --session <id> to continue with full conversation history. For independent parallel tasks, use the Task tool with run_in_background: true.
Failure handling
- `script: tcgetattr/ioctl: Operation not supported on socket` (exit code 1): the
scriptcommand probes stdin withtcgetattrat startup and only toleratesENOTTY/ENODEVerrors. When Claude Code connects stdin via a socketpair, the kernel returnsEOPNOTSUPPinstead — whichscriptdoesn't whitelist, so it exits immediately. The script detects this automatically by probing withscript -q /dev/null truefirst and falls back to direct execution. Update to the latest version if you still see this error. - Exit code 137: the task was interrupted (user cancel or OOM). Not a Codex bug — retry or break the task into smaller pieces.
- `ERROR codex_core::codex: failed to load skill ...` in stderr: one of Codex's own installed skills has a broken YAML file. This warning is harmless and doesn't affect the current task — ignore it.
- `(no response from codex)` in the output file: Codex ran but produced no readable output. Check stderr for clues; the task may have hit a sandbox restriction.
Options
--workspace <path>— Target workspace directory (defaults to current directory).--file <path>— Point CodeX to key entry-point files (repeatable, workspace-relative or absolute). Don't duplicate their contents in the prompt.--session <id>— Resume a previous session for multi-turn conversation.--model <name>— Override model (default: uses Codex config).--reasoning <level>— Reasoning effort:low,medium,high(default:medium). Usehighfor code review, debugging, complex refactoring, or root cause analysis.--sandbox <mode>— Override sandbox policy (default: workspace-write via full-auto).--read-only— Read-only mode for pure discussion/analysis, no file changes.
Resume mode limitations
When using --session to resume a previous conversation, note these limitations:
- Must run in a git repository — The
codex exec resumecommand requires a git-trusted directory. It does not support--skip-git-repo-check. - Limited options — Resume mode only supports
-c/--configand--last. The following options are not supported in resume mode: --sandbox--full-auto--read-only--model--workspace(resumes in the original session's context)- Text output only — Resume mode returns plain text instead of JSON-structured output.
.runtime/
#!/usr/bin/env powershell
# Windows PowerShell 5.1+ compatible script
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[string]$Task,
[Alias('t')]
[string]$TaskText,
[Alias('w')]
[string]$Workspace = (Get-Location).Path,
[Alias('f')]
[string[]]$File,
[string]$Session,
[string]$Model,
[ValidateSet('low', 'medium', 'high')]
[string]$Reasoning = 'medium',
[string]$Sandbox,
[switch]$ReadOnly,
[switch]$FullAuto,
[Alias('o')]
[string]$Output,
[switch]$Help
)
$ErrorActionPreference = 'Stop'
function Show-Usage {
@'
Usage:
ask_codex.ps1 <task> [options]
ask_codex.ps1 -Task <task> [options]
Task input:
<task> First positional argument is the task text
-Task, -t <text> Alias for positional task
File context (optional, repeatable):
-File, -f <path> Priority file path
Multi-turn:
-Session <id> Resume a previous session (thread_id from prior run)
Options:
-Workspace, -w <path> Workspace directory (default: current directory)
-Model <name> Model override
-Reasoning <level> Reasoning effort: low, medium, high (default: medium)
-Sandbox <mode> Sandbox mode override
-ReadOnly Read-only sandbox (no file changes)
-FullAuto Full-auto mode (default)
-Output, -o <path> Output file path
-Help Show this help
Output (on success):
session_id=<thread_id> Use with -Session for follow-up calls
output_path=<file> Path to response markdown
Examples:
# New task (positional)
ask_codex.ps1 "Add error handling to api.ts" -f src/api.ts
# With explicit workspace
ask_codex.ps1 "Fix the bug" -w C:\other\repo
# Continue conversation
ask_codex.ps1 "Also add retry logic" -Session <id>
'@
}
function Test-Command {
param([string]$Name)
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
Write-Error "[ERROR] Missing required command: $Name"
exit 1
}
}
function Trim-Whitespace {
param([string]$Text)
if ([string]::IsNullOrEmpty($Text)) { return '' }
return $Text.Trim() -replace '\s+', ' '
}
function Resolve-FileRef {
param(
[string]$Workspace,
[string]$RawPath
)
$cleaned = Trim-Whitespace $RawPath
if ([string]::IsNullOrWhiteSpace($cleaned)) { return '' }
# Remove line number suffixes (#L123 or :123-456)
$cleaned = $cleaned -replace '#L\d+$', ''
$cleaned = $cleaned -replace ':\d+(-\d+)?$', ''
# Make absolute if relative
if (-not [System.IO.Path]::IsPathRooted($cleaned)) {
$cleaned = Join-Path $Workspace $cleaned
}
# Normalize path
if (Test-Path $cleaned) {
return (Resolve-Path $cleaned -ErrorAction SilentlyContinue).Path
}
return $cleaned
}
function Write-File-NoBOM {
param([string]$Path, [string]$Content)
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
}
# Show help if requested
if ($Help) {
Show-Usage
exit 0
}
# Check required commands
Test-Command 'codex'
Test-Command 'jq'
# Resolve task text from either positional or named parameter
if ([string]::IsNullOrEmpty($Task) -and -not [string]::IsNullOrEmpty($TaskText)) {
$Task = $TaskText
}
# Validate workspace
if (-not (Test-Path $Workspace -PathType Container)) {
Write-Error "[ERROR] Workspace does not exist: $Workspace"
exit 1
}
$Workspace = (Resolve-Path $Workspace).Path
# Validate task
$Task = Trim-Whitespace $Task
if ([string]::IsNullOrEmpty($Task)) {
Write-Error "[ERROR] Request text is empty. Pass a positional arg or -Task."
exit 1
}
# Prepare output path
if ([string]::IsNullOrEmpty($Output)) {
$timestamp = (Get-Date).ToUniversalTime().ToString('yyyyMMdd-HHmmss')
$skillDir = Split-Path $PSScriptRoot -Parent
$runtimeDir = Join-Path $skillDir '.runtime'
if (-not (Test-Path $runtimeDir)) {
New-Item -ItemType Directory -Path $runtimeDir -Force | Out-Null
}
$Output = Join-Path $runtimeDir "$timestamp.md"
}
# Build file context block
$fileBlock = ''
if ($File -and $File.Count -gt 0) {
$fileBlock = "`nPriority files (read these first before making changes):"
foreach ($ref in $File) {
$resolved = Resolve-FileRef -Workspace $Workspace -RawPath $ref
if (-not [string]::IsNullOrEmpty($resolved)) {
$existsTag = if (Test-Path $resolved) { 'exists' } else { 'missing' }
$fileBlock += "`n- $resolved ($existsTag)"
}
}
}
# Build prompt
$prompt = $Task
if (-not [string]::IsNullOrEmpty($fileBlock)) {
$prompt += $fileBlock
}
# Build codex command
$codexArgs = @()
if (-not [string]::IsNullOrEmpty($Session)) {
# Resume mode: continue a previous session
# Note: resume only supports -c/--config and --last flags (no --json, --sandbox, etc.)
$codexArgs = @('exec', 'resume', '-c', "model_reasoning_effort=`"$Reasoning`"", '-c', 'skip_git_repo_check=true')
$codexArgs += $Session
} else {
# New session
$codexArgs = @('exec', '--cd', $Workspace, '--skip-git-repo-check', '--json', '-c', "model_reasoning_effort=`"$Reasoning`"")
if ($ReadOnly) {
$codexArgs += '--sandbox', 'read-only'
} elseif (-not [string]::IsNullOrEmpty($Sandbox)) {
$codexArgs += '--sandbox', $Sandbox
} elseif ($FullAuto) {
$codexArgs += '--full-auto'
}
if (-not [string]::IsNullOrEmpty($Model)) {
$codexArgs += '-m', $Model
}
}
# Create temp files
$tempDir = [System.IO.Path]::GetTempPath()
$guid = [guid]::NewGuid().ToString()
$stderrFile = Join-Path $tempDir "codex_stderr_$guid.txt"
$jsonFile = Join-Path $tempDir "codex_json_$guid.txt"
$promptFile = Join-Path $tempDir "codex_prompt_$guid.txt"
# Cleanup function
$cleanupScript = {
Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $jsonFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $promptFile -Force -ErrorAction SilentlyContinue
}
try {
# Write prompt to temp file (UTF-8 without BOM)
Write-File-NoBOM -Path $promptFile -Content $prompt
# Initialize json file
Write-File-NoBOM -Path $jsonFile -Content ''
# Setup process with async reading for real-time output
# On Windows, codex is installed as a .ps1 script, so we need to use cmd.exe or pwsh to run it
$psi = New-Object System.Diagnostics.ProcessStartInfo
if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) {
# Use cmd.exe to run codex (works with .cmd/.ps1 wrappers)
$psi.FileName = 'cmd.exe'
$psi.Arguments = '/c codex ' + ($codexArgs -join ' ')
} else {
$psi.FileName = 'codex'
$psi.Arguments = $codexArgs -join ' '
}
$psi.WorkingDirectory = $Workspace
$psi.UseShellExecute = $false
$psi.RedirectStandardInput = $true
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.CreateNoWindow = $true
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
# StringBuilder for collecting output
$jsonOutput = New-Object System.Text.StringBuilder
$stderrOutput = New-Object System.Text.StringBuilder
$outputLock = New-Object Object
# Event handler script blocks
$jsonOutputRef = $jsonOutput
$stderrOutputRef = $stderrOutput
# Register event handlers for async reading
$isResumeMode = -not [string]::IsNullOrEmpty($Session)
$textOutput = New-Object System.Text.StringBuilder
$stdOutAction = {
param([object]$sender, [System.Diagnostics.DataReceivedEventArgs]$e)
if ($e.Data) {
$line = $e.Data
# Strip terminal artifacts
$line = $line -replace "`r", ''
$line = $line -replace [char]4, ''
if (-not [string]::IsNullOrEmpty($line)) {
if ($line.StartsWith('{')) {
# JSON line (new session mode)
[System.Threading.Monitor]::Enter($Event.MessageData)
try {
$Event.MessageData.AppendLine($line) | Out-Null
} finally {
[System.Threading.Monitor]::Exit($Event.MessageData)
}
# Print progress for relevant events
if ($line -match '"item\.started"' -or $line -match '"item\.completed"') {
if ($line -match '"item\.started"' -and $line -match '"command_execution"') {
try {
$json = $line | ConvertFrom-Json -ErrorAction SilentlyContinue
$cmd = $json.item.command
if ($cmd) {
$cmd = $cmd -replace '^/bin/(zsh|bash) (-lc|-c) ', ''
if ($cmd.Length -gt 100) { $cmd = $cmd.Substring(0, 100) }
Write-Host "[codex] > $cmd" -ForegroundColor Gray
}
} catch {}
}
if ($line -match '"item\.completed"' -and $line -match '"agent_message"') {
try {
$json = $line | ConvertFrom-Json -ErrorAction SilentlyContinue
$text = $json.item.text
if ($text) {
$preview = $text.Split("`n")[0]
if ($preview.Length -gt 120) { $preview = $preview.Substring(0, 120) }
Write-Host "[codex] $preview" -ForegroundColor Gray
}
} catch {}
}
}
} else {
# Plain text line (resume mode)
[System.Threading.Monitor]::Enter($Event.MessageData)
try {
$Event.MessageData.AppendLine($line) | Out-Null
} finally {
[System.Threading.Monitor]::Exit($Event.MessageData)
}
# Show progress for text output
$preview = $line
if ($preview.Length -gt 120) { $preview = $preview.Substring(0, 120) }
Write-Host "[codex] $preview" -ForegroundColor Gray
}
}
}
}
$stdErrAction = {
param([object]$sender, [System.Diagnostics.DataReceivedEventArgs]$e)
if ($e.Data) {
[System.Threading.Monitor]::Enter($Event.MessageData)
try {
$Event.MessageData.AppendLine($e.Data) | Out-Null
} finally {
[System.Threading.Monitor]::Exit($Event.MessageData)
}
Write-Host $e.Data -ForegroundColor Yellow
}
}
# Register events - use textOutput for resume mode, jsonOutput for new session
$outputData = if ($isResumeMode) { $textOutput } else { $jsonOutput }
$stdOutEvent = Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -Action $stdOutAction -MessageData $outputData
$stdErrEvent = Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -Action $stdErrAction -MessageData $stderrOutput
try {
# Start process
$process.Start() | Out-Null
# Begin async reading
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
# Write prompt to stdin
$process.StandardInput.Write($prompt)
$process.StandardInput.Close()
# Wait for process to exit
$process.WaitForExit()
$exitCode = $process.ExitCode
} finally {
# Unregister events
Unregister-Event -SourceIdentifier $stdOutEvent.Name -ErrorAction SilentlyContinue
Unregister-Event -SourceIdentifier $stdErrEvent.Name -ErrorAction SilentlyContinue
$process.Dispose()
}
# Process output based on mode
$threadId = $null
$outputContent = @()
if ($isResumeMode) {
# Resume mode: plain text output
$textContent = $textOutput.ToString().Trim()
# Check for errors
$stderrText = $stderrOutput.ToString()
$hasValidOutput = -not [string]::IsNullOrWhiteSpace($textContent)
if ($stderrText -match '\[ERROR\]' -and -not $hasValidOutput) {
Write-Error "[ERROR] Codex command failed"
Write-Error $stderrText
exit 1
}
if ($exitCode -ne 0 -and -not $hasValidOutput) {
Write-Error "[ERROR] Codex exited with code $exitCode"
exit 1
}
# Use session ID from parameter
$threadId = $Session
if (-not [string]::IsNullOrWhiteSpace($textContent)) {
$outputContent += $textContent
}
} else {
# New session mode: JSON output
$jsonText = $jsonOutput.ToString()
Write-File-NoBOM -Path $jsonFile -Content $jsonText
# Check for errors - but only fail if no valid output was received
$stderrText = $stderrOutput.ToString()
$hasValidOutput = -not [string]::IsNullOrWhiteSpace($jsonText) -and $jsonText -match '"thread_id"'
if ($stderrText -match '\[ERROR\]' -and -not $hasValidOutput) {
Write-Error "[ERROR] Codex command failed"
Write-Error $stderrText
exit 1
}
if ($exitCode -ne 0 -and -not $hasValidOutput) {
Write-Error "[ERROR] Codex exited with code $exitCode"
exit 1
}
# Extract thread_id and messages from JSON stream
if (-not [string]::IsNullOrWhiteSpace($jsonText)) {
# Find thread_id
if ($jsonText -match '"thread_id"\s*:\s*"([^"]+)"') {
$threadId = $matches[1]
}
# Parse JSON lines using PowerShell native parsing (more reliable on Windows)
$jsonLines = $jsonText -split "`n" | Where-Object { $_.Trim() -and $_.TrimStart().StartsWith('{') }
foreach ($line in $jsonLines) {
try {
$obj = $line | ConvertFrom-Json -ErrorAction SilentlyContinue
if (-not $obj) { continue }
# Process completed items
if ($obj.type -eq 'item.completed' -and $obj.item) {
$item = $obj.item
# Agent messages
if ($item.type -eq 'agent_message' -and $item.text) {
$outputContent += $item.text
}
# Command executions
if ($item.type -eq 'command_execution' -and $item.command) {
$cmd = $item.command -replace '^/bin/(zsh|bash) (-lc|-c) ', ''
$cmdPreview = $cmd.Substring(0, [Math]::Min(200, $cmd.Length))
$outPreview = ''
if ($item.aggregated_output) {
$outPreview = $item.aggregated_output.Substring(0, [Math]::Min(500, $item.aggregated_output.Length))
}
$outputContent += "### Shell: ``$cmdPreview```n$outPreview"
}
# Tool calls (file operations)
if ($item.type -eq 'tool_call' -and $item.name) {
$args = $null
try {
$args = $item.arguments | ConvertFrom-Json -ErrorAction SilentlyContinue
} catch {}
if ($item.name -eq 'write_file' -and $args.path) {
$outputContent += "### File written: $($args.path)"
}
if ($item.name -eq 'patch_file' -and $args.path) {
$outputContent += "### File patched: $($args.path)"
}
if ($item.name -eq 'shell' -and $args.command) {
$cmdPreview = $args.command.Substring(0, [Math]::Min(200, $args.command.Length))
$outPreview = ''
if ($item.output) {
$outPreview = $item.output.Substring(0, [Math]::Min(500, $item.output.Length))
}
$outputContent += "### Shell: ``$cmdPreview```n$outPreview"
}
}
}
} catch {
# Skip malformed lines
}
}
}
}
# Ensure output directory exists
$outputDir = Split-Path $Output -Parent
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
}
# Write output
if ($outputContent.Count -gt 0) {
Write-File-NoBOM -Path $Output -Content ($outputContent -join "`n")
} else {
Write-File-NoBOM -Path $Output -Content "(no response from codex)"
}
# Output results
if (-not [string]::IsNullOrEmpty($threadId)) {
Write-Output "session_id=$threadId"
}
Write-Output "output_path=$Output"
} finally {
& $cleanupScript
}
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage:
ask_codex.sh <task> [options]
ask_codex.sh -t <task> [options]
Task input:
<task> First positional argument is the task text
-t, --task <text> Alias for positional task (backward compat)
(stdin) Pipe task text via stdin if no arg/flag given
File context (optional, repeatable):
-f, --file <path> Priority file path
Multi-turn:
--session <id> Resume a previous session (thread_id from prior run)
Options:
-w, --workspace <path> Workspace directory (default: current directory)
--model <name> Model override
--reasoning <level> Reasoning effort: low, medium, high (default: medium)
--sandbox <mode> Sandbox mode override
--read-only Read-only sandbox (no file changes)
--full-auto Full-auto mode (default)
-o, --output <path> Output file path
-h, --help Show this help
Output (on success):
session_id=<thread_id> Use with --session for follow-up calls
output_path=<file> Path to response markdown
Examples:
# New task (positional)
ask_codex.sh "Add error handling to api.ts" -f src/api.ts
# With explicit workspace
ask_codex.sh "Fix the bug" -w /other/repo
# Continue conversation
ask_codex.sh "Also add retry logic" --session <id>
USAGE
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "[ERROR] Missing required command: $1" >&2
exit 1
fi
}
trim_whitespace() {
awk 'BEGIN { RS=""; ORS="" } { gsub(/^[ \t\r\n]+|[ \t\r\n]+$/, ""); print }' <<<"$1"
}
to_abs_if_exists() {
local target="$1"
if [[ -e "$target" ]]; then
local dir
dir="$(cd "$(dirname "$target")" && pwd)"
echo "$dir/$(basename "$target")"
return
fi
echo "$target"
}
resolve_file_ref() {
local workspace="$1" raw="$2" cleaned
cleaned="$(trim_whitespace "$raw")"
[[ -z "$cleaned" ]] && { echo ""; return; }
if [[ "$cleaned" =~ ^(.+)#L[0-9]+$ ]]; then cleaned="${BASH_REMATCH[1]}"; fi
if [[ "$cleaned" =~ ^(.+):[0-9]+(-[0-9]+)?$ ]]; then cleaned="${BASH_REMATCH[1]}"; fi
if [[ "$cleaned" != /* ]]; then cleaned="$workspace/$cleaned"; fi
to_abs_if_exists "$cleaned"
}
append_file_refs() {
local raw="$1" item
IFS=',' read -r -a items <<< "$raw"
for item in "${items[@]}"; do
local trimmed
trimmed="$(trim_whitespace "$item")"
[[ -n "$trimmed" ]] && file_refs+=("$trimmed")
done
}
# --- Parse arguments ---
workspace="${PWD}"
task_text=""
model=""
reasoning_effort=""
sandbox_mode=""
read_only=false
full_auto=true
output_path=""
session_id=""
file_refs=()
while [[ $# -gt 0 ]]; do
case "$1" in
-w|--workspace) workspace="${2:-}"; shift 2 ;;
-t|--task) task_text="${2:-}"; shift 2 ;;
-f|--file|--focus) append_file_refs "${2:-}"; shift 2 ;;
--model) model="${2:-}"; shift 2 ;;
--reasoning) reasoning_effort="${2:-}"; shift 2 ;;
--sandbox) sandbox_mode="${2:-}"; full_auto=false; shift 2 ;;
--read-only) read_only=true; full_auto=false; shift ;;
--full-auto) full_auto=true; shift ;;
--session) session_id="${2:-}"; shift 2 ;;
-o|--output) output_path="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
-*) echo "[ERROR] Unknown option: $1" >&2; usage >&2; exit 1 ;;
*) if [[ -z "$task_text" ]]; then task_text="$1"; shift; else echo "[ERROR] Unexpected argument: $1" >&2; usage >&2; exit 1; fi ;;
esac
done
require_cmd codex
require_cmd jq
# --- Validate inputs ---
if [[ ! -d "$workspace" ]]; then
echo "[ERROR] Workspace does not exist: $workspace" >&2; exit 1
fi
workspace="$(cd "$workspace" && pwd)"
if [[ -z "$task_text" && ! -t 0 ]]; then
task_text="$(cat)"
fi
task_text="$(trim_whitespace "$task_text")"
if [[ -z "$task_text" ]]; then
echo "[ERROR] Request text is empty. Pass a positional arg, --task, or stdin." >&2; exit 1
fi
# --- Prepare output path ---
if [[ -z "$output_path" ]]; then
timestamp="$(date -u +"%Y%m%d-%H%M%S")"
skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
output_path="$skill_dir/.runtime/${timestamp}.md"
fi
mkdir -p "$(dirname "$output_path")"
# --- Build file context block ---
file_block=""
if (( ${#file_refs[@]} > 0 )); then
file_block=$'\nPriority files (read these first before making changes):'
for ref in "${file_refs[@]}"; do
resolved="$(resolve_file_ref "$workspace" "$ref")"
[[ -z "$resolved" ]] && continue
exists_tag="missing"
[[ -e "$resolved" ]] && exists_tag="exists"
file_block+=$'\n- '"${resolved} (${exists_tag})"
done
fi
# --- Build prompt ---
prompt="$task_text"
if [[ -n "$file_block" ]]; then
prompt+=$'\n'"$file_block"
fi
# --- Determine reasoning effort ---
if [[ -z "$reasoning_effort" ]]; then
reasoning_effort="medium"
fi
# --- Build codex command ---
if [[ -n "$session_id" ]]; then
# Resume mode: continue a previous session
# Note: resume only supports -c/--config and --last flags (no --json, --sandbox, etc.)
cmd=(codex exec resume -c "model_reasoning_effort=\"$reasoning_effort\"" -c "skip_git_repo_check=true")
cmd+=("$session_id")
else
# New session
cmd=(codex exec --cd "$workspace" --skip-git-repo-check --json -c "model_reasoning_effort=\"$reasoning_effort\"")
if [[ "$read_only" == true ]]; then
cmd+=(--sandbox read-only)
elif [[ -n "$sandbox_mode" ]]; then
cmd+=(--sandbox "$sandbox_mode")
elif [[ "$full_auto" == true ]]; then
cmd+=(--full-auto)
fi
[[ -n "$model" ]] && cmd+=(-m "$model")
fi
# --- Progress watcher function ---
print_progress() {
local line="$1"
local item_type cmd_str preview
# Fast string checks before calling jq
case "$line" in
*'"item.started"'*'"command_execution"'*)
cmd_str=$(printf '%s' "$line" | jq -r '.item.command // empty' 2>/dev/null | sed 's|^/bin/zsh -lc ||; s|^/bin/bash -c ||' | cut -c1-100)
[[ -n "$cmd_str" ]] && echo "[codex] > $cmd_str" >&2
;;
*'"item.completed"'*'"agent_message"'*)
preview=$(printf '%s' "$line" | jq -r '.item.text // empty' 2>/dev/null | head -1 | cut -c1-120)
[[ -n "$preview" ]] && echo "[codex] $preview" >&2
;;
esac
}
# --- Execute and capture output ---
stderr_file="$(mktemp)"
json_file="$(mktemp)"
text_file="$(mktemp)"
prompt_file="$(mktemp)"
trap 'rm -f "$stderr_file" "$json_file" "$text_file" "$prompt_file"' EXIT
# Write prompt to a temp file and pipe from there to avoid shell argument
# length issues and encoding problems with very long or multi-byte prompts.
printf "%s" "$prompt" > "$prompt_file"
# Run codex and capture its output.
# We prefer `script` to allocate a pseudo-TTY, which forces codex to line-buffer
# its output so progress events arrive in real time. However, `script` requires a
# real controlling terminal and fails with "tcgetattr/ioctl: Operation not supported
# on socket" in socket-based environments (e.g. some Claude Code sandboxes). We
# detect this upfront and fall back to direct execution — output may arrive all at
# once at the end, but the task still completes correctly.
run_codex() {
# BSD script (macOS): script [-q] [file [command...]]
# util-linux script (Linux): script [-q] -c <command> [file]
# Probe the local variant and use matching syntax for PTY allocation.
# Falls back to direct execution if neither probe succeeds (e.g. socket stdin).
local os
os="$(uname -s)"
if [[ "$os" == "Darwin" ]]; then
if script -q /dev/null true >/dev/null 2>&1; then
script -q /dev/null /bin/bash -c \
"cd $(printf '%q' "$workspace") && $(printf '%q ' "${cmd[@]}") < $(printf '%q' "$prompt_file") 2>$(printf '%q' "$stderr_file")"
return
fi
else
if script -q -c "true" /dev/null >/dev/null 2>&1; then
script -q -c \
"cd $(printf '%q' "$workspace") && $(printf '%q ' "${cmd[@]}") < $(printf '%q' "$prompt_file") 2>$(printf '%q' "$stderr_file")" \
/dev/null
return
fi
fi
# Fallback: direct execution (no PTY; progress events arrive in batch)
(cd "$workspace" && "${cmd[@]}" < "$prompt_file" 2>"$stderr_file")
}
if [[ -n "$session_id" ]]; then
# Resume mode: plain text output (no JSON support)
run_codex | while IFS= read -r line; do
# Strip terminal artifacts (carriage return, ^D EOF marker)
cleaned="${line//$'\r'/}"
cleaned="${cleaned//$'\004'/}"
[[ -z "$cleaned" ]] && continue
# Write to text_file for later output
printf '%s\n' "$cleaned" >> "$text_file"
# Print progress
preview="${cleaned:0:120}"
echo "[codex] $preview" >&2
done
else
# New session: JSON output
run_codex | while IFS= read -r line; do
# Strip terminal artifacts (carriage return, ^D EOF marker)
cleaned="${line//$'\r'/}"
cleaned="${cleaned//$'\004'/}"
[[ -z "$cleaned" ]] && continue
# Only process JSON lines (must start with '{')
[[ "$cleaned" != \{* ]] && continue
# Write to json_file for later parsing
printf '%s\n' "$cleaned" >> "$json_file"
# Only parse progress-relevant events (fast string check before jq)
case "$cleaned" in
*'"item.started"'*|*'"item.completed"'*) print_progress "$cleaned" ;;
esac
done
fi
if [[ -s "$stderr_file" ]] && grep -q '\[ERROR\]' "$stderr_file" 2>/dev/null; then
echo "[ERROR] Codex command failed" >&2
cat "$stderr_file" >&2
exit 1
fi
if [[ -s "$stderr_file" ]]; then
cat "$stderr_file" >&2
fi
# --- Process output based on mode ---
if [[ -n "$session_id" ]]; then
# Resume mode: use plain text output
thread_id="$session_id"
if [[ -s "$text_file" ]]; then
cat "$text_file" > "$output_path"
else
echo "(no response from codex)" > "$output_path"
fi
else
# New session: Extract thread_id and all messages from JSON stream
thread_id="$(jq -r 'select(.type == "thread.started") | .thread_id' < "$json_file" | head -1)"
# Collect all completed items: file changes, tool calls, and agent messages.
# This gives full visibility into what codex actually did, not just the last message.
{
# 1. Show command executions — skip pure file-reading/searching commands.
# Codex explores the codebase heavily (sed/cat/nl/rg/grep/awk/wc/find/ls), but
# those reads produce no signal for Claude Code — it can read files directly if needed.
# Keep build, test, git, and mutation commands that reflect actual work done.
#
# Note: zsh wraps commands in quotes, so after stripping the shell prefix the
# command may start with " or ' — the regex accounts for this with [\"']?.
jq -r '
select(.type == "item.completed" and .item.type == "command_execution")
| .item
| ((.command // "") | gsub("^/bin/zsh -lc "; "") | gsub("^/bin/bash -c "; "")) as $cmd
| select($cmd | test("^[\"'"'"']?(sed |cat |head |tail |nl |rg |grep |awk |wc |find |ls )") | not)
| "### Shell: `" + ($cmd[0:200]) + "`\n" + (.aggregated_output // "" | .[0:500])
' < "$json_file" 2>/dev/null
# 2. Show file write/patch operations (tool_call style, if any)
jq -r '
select(.type == "item.completed" and .item.type == "tool_call")
| .item
| if .name == "write_file" then
"### File written: " + (.arguments | fromjson | .path // "unknown")
elif .name == "patch_file" then
"### File patched: " + (.arguments | fromjson | .path // "unknown")
elif .name == "shell" then
"### Shell: `" + (.arguments | fromjson | .command // "unknown")[0:200] + "`\n" + (.output // "" | .[0:500])
else empty
end
' < "$json_file" 2>/dev/null
# 3. Show all agent messages. Short messages (lint results, "tests failed",
# "no changes needed") carry high signal and must not be dropped by a length
# threshold. In practice, Codex tends to emit a small number of large blocks
# rather than many tiny fragments, so this produces clean output without filtering.
jq -r '
select(.type == "item.completed" and .item.type == "agent_message") | .item.text
' < "$json_file" 2>/dev/null
} > "$output_path"
# If nothing was captured, write a fallback
if [[ ! -s "$output_path" ]]; then
echo "(no response from codex)" > "$output_path"
fi
fi
# --- Output results ---
if [[ -n "$thread_id" ]]; then
echo "session_id=$thread_id"
fi
echo "output_path=$output_path"
Related skills
How it compares
Pick the codex skill over inline Claude coding when the user explicitly wants Codex CLI's autonomous exploration and implementation in the same repository.
FAQ
When should the codex skill run?
The codex skill runs only when the user explicitly asks to use Codex—for example "ask codex to" or equivalent phrasing. The skill does not proactively delegate general coding requests the user did not specifically assign to Codex CLI.
Why use the bundled script for Codex?
The codex skill uses a bundled shell script rather than calling the codex CLI directly. The script handles output formatting and execution consistency while Codex autonomously reads files, greps the codebase, and runs bash to implement changes.
Is Codex safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.