From 71305da77e2e6aa0ac790c3300876be0cce46c4e Mon Sep 17 00:00:00 2001 From: cexll Date: Tue, 25 Nov 2025 21:00:12 +0800 Subject: [PATCH] fix codex skill eof --- skills/codex/SKILL.md | 102 ++++++++++++++++++++++------------ skills/codex/scripts/codex.py | 52 ++++++++++------- 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/skills/codex/SKILL.md b/skills/codex/SKILL.md index 191e0cc..f093098 100644 --- a/skills/codex/SKILL.md +++ b/skills/codex/SKILL.md @@ -17,25 +17,40 @@ Execute Codex CLI commands and parse structured JSON responses. Supports file re ## Usage -**Mandatory**: Run every automated invocation through the Bash tool in the foreground with the command below, keeping the `timeout` parameter fixed at `7200000` milliseconds (do not change it or use any other entry point). +**Mandatory**: Run every automated invocation through the Bash tool in the foreground with **HEREDOC syntax** to avoid shell quoting issues, keeping the `timeout` parameter fixed at `7200000` milliseconds (do not change it or use any other entry point). + ```bash -uv run ~/.claude/skills/codex/scripts/codex.py "" [working_dir] +uv run ~/.claude/skills/codex/scripts/codex.py - [working_dir] <<'EOF' + +EOF ``` -**Foreground only (no background/BashOutput)**: Never set `background: true`, never accept Claude's “Running in the background” mode, and avoid `BashOutput` streaming loops. Keep a single foreground Bash call per Codex task; if work might be long, split it into smaller foreground runs instead of offloading to background execution. +**Why HEREDOC?** Tasks often contain code blocks, nested quotes, shell metacharacters (`$`, `` ` ``, `\`), and multiline text. HEREDOC (Here Document) syntax passes these safely without shell interpretation, eliminating quote-escaping nightmares. -**Optional methods** (direct execution or via Python): +**Foreground only (no background/BashOutput)**: Never set `background: true`, never accept Claude's "Running in the background" mode, and avoid `BashOutput` streaming loops. Keep a single foreground Bash call per Codex task; if work might be long, split it into smaller foreground runs instead of offloading to background execution. + +**Simple tasks** (backward compatibility): +For simple single-line tasks without special characters, you can still use direct quoting: ```bash -~/.claude/skills/codex/scripts/codex.py "" [working_dir] -# or -python3 ~/.claude/skills/codex/scripts/codex.py "" [working_dir] +uv run ~/.claude/skills/codex/scripts/codex.py "simple task here" [working_dir] ``` -Resume a session: +**Resume a session with HEREDOC:** ```bash -uv run ~/.claude/skills/codex/scripts/codex.py resume "" [working_dir] +uv run ~/.claude/skills/codex/scripts/codex.py resume - [working_dir] <<'EOF' + +EOF ``` +**Cross-platform notes:** +- **Bash/Zsh**: Use `<<'EOF'` (single quotes prevent variable expansion) +- **PowerShell 5.1+**: Use `@'` and `'@` (here-string syntax) + ```powershell + uv run ~/.claude/skills/codex/scripts/codex.py - @' + task content + '@ + ``` + ## Environment Variables - **CODEX_TIMEOUT**: Override timeout in milliseconds (default: 7200000 = 2 hours) - Example: `export CODEX_TIMEOUT=3600000` for 1 hour @@ -72,63 +87,82 @@ Return only the final agent message and session ID—do not paste raw `BashOutpu ### Invocation Pattern -All automated executions may only invoke `uv run ~/.claude/skills/codex/scripts/codex.py "" ...` through the Bash tool in the foreground, and the `timeout` must remain fixed at `7200000` (non-negotiable): +All automated executions must use HEREDOC syntax through the Bash tool in the foreground, with `timeout` fixed at `7200000` (non-negotiable): + ``` Bash tool parameters: -- command: uv run ~/.claude/skills/codex/scripts/codex.py "" [working_dir] +- command: uv run ~/.claude/skills/codex/scripts/codex.py - [working_dir] <<'EOF' + + EOF - timeout: 7200000 - description: ``` + Run every call in the foreground—never append `&` to background it—so logs and errors stay visible for timely interruption or diagnosis. -Alternatives: -``` -# Direct execution (simplest) -- command: ~/.claude/skills/codex/scripts/codex.py "" [working_dir] - -# Using python3 -- command: python3 ~/.claude/skills/codex/scripts/codex.py "" [working_dir] -``` +**Important:** Use HEREDOC (`<<'EOF'`) for all but the simplest tasks. This prevents shell interpretation of quotes, variables, and special characters. ### Examples **Basic code analysis:** ```bash -# Recommended: via uv run (auto-manages Python environment) -uv run ~/.claude/skills/codex/scripts/codex.py "explain @src/main.ts" +# Recommended: via uv run with HEREDOC (handles any special characters) +uv run ~/.claude/skills/codex/scripts/codex.py - <<'EOF' +explain @src/main.ts +EOF # timeout: 7200000 -# Alternative: direct execution -~/.claude/skills/codex/scripts/codex.py "explain @src/main.ts" +# Alternative: simple direct quoting (if task is simple) +uv run ~/.claude/skills/codex/scripts/codex.py "explain @src/main.ts" ``` -**Refactoring with custom model (via environment variable):** +**Refactoring with multiline instructions:** ```bash -# Set model via environment variable -uv run ~/.claude/skills/codex/scripts/codex.py "refactor @src/utils for performance" +uv run ~/.claude/skills/codex/scripts/codex.py - <<'EOF' +refactor @src/utils for performance: +- Extract duplicate code into helpers +- Use memoization for expensive calculations +- Add inline comments for non-obvious logic +EOF # timeout: 7200000 ``` **Multi-file analysis:** ```bash -uv run ~/.claude/skills/codex/scripts/codex.py "analyze @. and find security issues" "/path/to/project" +uv run ~/.claude/skills/codex/scripts/codex.py - "/path/to/project" <<'EOF' +analyze @. and find security issues: +1. Check for SQL injection vulnerabilities +2. Identify XSS risks in templates +3. Review authentication/authorization logic +4. Flag hardcoded credentials or secrets +EOF # timeout: 7200000 ``` **Resume previous session:** ```bash # First session -uv run ~/.claude/skills/codex/scripts/codex.py "add comments to @utils.js" +uv run ~/.claude/skills/codex/scripts/codex.py - <<'EOF' +add comments to @utils.js explaining the caching logic +EOF # Output includes: SESSION_ID: 019a7247-ac9d-71f3-89e2-a823dbd8fd14 -# Continue the conversation -uv run ~/.claude/skills/codex/scripts/codex.py resume 019a7247-ac9d-71f3-89e2-a823dbd8fd14 "now add type hints" +# Continue the conversation with more context +uv run ~/.claude/skills/codex/scripts/codex.py resume 019a7247-ac9d-71f3-89e2-a823dbd8fd14 - <<'EOF' +now add TypeScript type hints and handle edge cases where cache is null +EOF # timeout: 7200000 ``` -**Using python3 directly (alternative):** +**Task with code snippets and special characters:** ```bash -python3 ~/.claude/skills/codex/scripts/codex.py "your task here" +uv run ~/.claude/skills/codex/scripts/codex.py - <<'EOF' +Fix the bug in @app.js where the regex /\d+/ doesn't match "123" +The current code is: + const re = /\d+/; + if (re.test(input)) { ... } +Add proper escaping and handle $variables correctly. +EOF ``` ### Large Task Protocol @@ -139,8 +173,8 @@ python3 ~/.claude/skills/codex/scripts/codex.py "your task here" | ID | Description | Scope | Dependencies | Tests | Command | | --- | --- | --- | --- | --- | --- | -| T1 | Review @spec.md to extract requirements | docs/, @spec.md | None | None | uv run ~/.claude/skills/codex/scripts/codex.py "analyze requirements @spec.md" | -| T2 | Implement the module and add test cases | src/module | T1 | npm test -- --runInBand | uv run ~/.claude/skills/codex/scripts/codex.py "implement and test @src/module" | +| T1 | Review @spec.md to extract requirements | docs/, @spec.md | None | None | `uv run ~/.claude/skills/codex/scripts/codex.py - <<'EOF'`
`analyze requirements @spec.md`
`EOF` | +| T2 | Implement the module and add test cases | src/module | T1 | npm test -- --runInBand | `uv run ~/.claude/skills/codex/scripts/codex.py - <<'EOF'`
`implement and test @src/module`
`EOF` | ## Notes diff --git a/skills/codex/scripts/codex.py b/skills/codex/scripts/codex.py index bd76309..aaa0cea 100755 --- a/skills/codex/scripts/codex.py +++ b/skills/codex/scripts/codex.py @@ -9,7 +9,9 @@ Codex CLI wrapper with cross-platform support and session management. Usage: New session: uv run codex.py "task" [workdir] + Stdin mode: uv run codex.py - [workdir] Resume: uv run codex.py resume "task" [workdir] + Resume stdin: uv run codex.py resume - [workdir] Alternative: python3 codex.py "task" Direct exec: ./codex.py "task" @@ -80,19 +82,23 @@ def parse_args(): if len(sys.argv) < 4: log_error('Resume mode requires: resume ') sys.exit(1) + task_arg = sys.argv[3] return { 'mode': 'resume', 'session_id': sys.argv[2], - 'task': sys.argv[3], - 'workdir': sys.argv[4] if len(sys.argv) > 4 else DEFAULT_WORKDIR - } - else: - return { - 'mode': 'new', - 'task': sys.argv[1], - 'workdir': sys.argv[2] if len(sys.argv) > 2 else DEFAULT_WORKDIR + 'task': task_arg, + 'explicit_stdin': task_arg == '-', + 'workdir': sys.argv[4] if len(sys.argv) > 4 else DEFAULT_WORKDIR, } + task_arg = sys.argv[1] + return { + 'mode': 'new', + 'task': task_arg, + 'explicit_stdin': task_arg == '-', + 'workdir': sys.argv[2] if len(sys.argv) > 2 else DEFAULT_WORKDIR, + } + def read_piped_task() -> Optional[str]: """ @@ -100,19 +106,10 @@ def read_piped_task() -> Optional[str]: - 如果 stdin 是管道(非 tty)且存在内容,返回读取到的字符串 - 否则返回 None """ - import select - stdin = sys.stdin if stdin is None or stdin.isatty(): log_info("Stdin is tty or None, skipping pipe read") return None - - # 使用 select 检查是否有数据可读(0 秒超时,非阻塞) - readable, _, _ = select.select([stdin], [], [], 0) - if not readable: - log_info("No data available on stdin") - return None - log_info("Reading from stdin pipe...") data = stdin.read() if not data: @@ -153,6 +150,7 @@ def build_codex_args(params: dict, target_arg: str) -> list: if params['mode'] == 'resume': return [ 'codex', 'e', + '-m', DEFAULT_MODEL, '--skip-git-repo-check', '--json', 'resume', @@ -276,16 +274,28 @@ def main(): timeout_sec = resolve_timeout() log_info(f"Timeout: {timeout_sec}s") - piped_task = read_piped_task() - piped = piped_task is not None - task_text = piped_task if piped else params['task'] + explicit_stdin = params.get('explicit_stdin', False) - use_stdin = should_stream_via_stdin(task_text, piped) + if explicit_stdin: + log_info("Explicit stdin mode: reading task from stdin") + task_text = sys.stdin.read() + if not task_text: + log_error("Explicit stdin mode requires task input from stdin") + sys.exit(1) + piped = not sys.stdin.isatty() + else: + piped_task = read_piped_task() + piped = piped_task is not None + task_text = piped_task if piped else params['task'] + + use_stdin = explicit_stdin or should_stream_via_stdin(task_text, piped) if use_stdin: reasons = [] if piped: reasons.append('piped input') + if explicit_stdin: + reasons.append('explicit "-"') if '\n' in task_text: reasons.append('newline') if '\\' in task_text: