openai/codex-action
Publicmirrored from https://github.com/openai/codex-actionAvailable
action.yml
196lines · modecode
| 1 | name: "Codex Exec Action" |
| 2 | description: "Run `codex exec` with a prompt." |
| 3 | author: "OpenAI" |
| 4 | inputs: |
| 5 | prompt: |
| 6 | description: "Prompt to pass to `codex exec`. `prompt` or `prompt_file` must be provided." |
| 7 | required: false |
| 8 | default: "" |
| 9 | prompt_file: |
| 10 | description: "Path to file that contains the prompt to pass to `codex exec`. `prompt` or `prompt_file` must be provided." |
| 11 | required: false |
| 12 | default: "" |
| 13 | openai_api_key: |
| 14 | description: "OpenAI API key used by the Codex CLI." |
| 15 | required: true |
| 16 | codex_working_directory: |
| 17 | description: "Optional working directory to `cd` into before running `codex exec`." |
| 18 | required: false |
| 19 | default: "" |
| 20 | codex_version: |
| 21 | description: "Version of `@openai/codex` to install." |
| 22 | required: false |
| 23 | default: "0.43.0-alpha.3" |
| 24 | codex_args: |
| 25 | description: "Additional args to pass through to `codex exec`. If this value starts with `[`, it will be parsed as a JSON array; otherwise, it will be parsed as a shell-like string." |
| 26 | required: false |
| 27 | default: "" |
| 28 | codex_output_file: |
| 29 | description: "File path where the Codex output should be written. Ignored if the empty string." |
| 30 | required: false |
| 31 | default: "" |
| 32 | codex_home: |
| 33 | description: "Directory to use as the Codex home directory. If empty, the default Codex home directory will be used." |
| 34 | required: false |
| 35 | default: "" |
| 36 | safety_strategy: |
| 37 | description: | |
| 38 | Specify one of the following options (on Windows, the only supported option is `unsafe`): |
| 39 | |
| 40 | * `drop_sudo` (default, IRREVERSIBLE) Drop sudo privileges (if any) from |
| 41 | the default user before running Codex, and run Codex as that user. This |
| 42 | is only supported on Linux and macOS runners. This option is |
| 43 | irreversible: if the default user has sudo privileges, they will be |
| 44 | removed permanently for the duration of the job. |
| 45 | * `unprivileged_user` Run Codex as the specified user specified by the |
| 46 | `codex_user` option (the user must already exist). Note the caller is |
| 47 | responsible for ensuring the specified user has the privileges it needs |
| 48 | to perform the requested actions. For example, the copy of the repo |
| 49 | created by `actions/checkout` is not world-readable by default. |
| 50 | * `read_only` Run Codex in a sandbox that can read any file on disk, |
| 51 | but cannot write to disk or access the network. Note Codex will still |
| 52 | run as the default user for this Action, which likely has sudo |
| 53 | privileges, so it could read `openai_api_key` from memory and reveal it |
| 54 | by printing it to the output of the GitHub Action. |
| 55 | * `unsafe` (NOT RECOMMENDED) Do not try to restrict Codex's privileges at all. |
| 56 | This is extremely dangerous, as the default user for this Action likely |
| 57 | has sudo privileges, which means it can read secrets stored in memory |
| 58 | (such as the value of `openai_api_key`) and print them to the output |
| 59 | of the GitHub Action or exfiltrate them in other ways. |
| 60 | required: false |
| 61 | default: "drop_sudo" |
| 62 | codex_user: |
| 63 | description: "If `safety_strategy` is set to `unprivileged_user`, this specifies the UNIX username to run Codex as." |
| 64 | required: false |
| 65 | default: "" |
| 66 | require_repo_write: |
| 67 | description: "Whether to require the triggering actor to have write access to the repository before running." |
| 68 | required: false |
| 69 | default: "true" |
| 70 | allow_bots: |
| 71 | description: "Allow runs triggered by GitHub Apps/bot accounts to bypass the write-access check." |
| 72 | required: false |
| 73 | default: "false" |
| 74 | outputs: |
| 75 | final_message: |
| 76 | description: "Raw output emitted by `codex exec`." |
| 77 | value: ${{ steps.run_codex.outputs.final_message }} |
| 78 | runs: |
| 79 | using: "composite" |
| 80 | steps: |
| 81 | - name: Validate Windows safety strategy |
| 82 | if: ${{ runner.os == 'Windows' }} |
| 83 | shell: bash |
| 84 | run: | |
| 85 | if [ "${{ inputs.safety_strategy }}" != "unsafe" ]; then |
| 86 | echo "On Windows, inputs.safety_strategy must be 'unsafe'" >&2 |
| 87 | echo "because no viable sandboxing options are available at this time." >&2 |
| 88 | exit 1 |
| 89 | fi |
| 90 | |
| 91 | - name: Ensure Node.js available |
| 92 | uses: actions/setup-node@v4 |
| 93 | with: |
| 94 | node-version: "20" |
| 95 | |
| 96 | - name: Check repository write access |
| 97 | if: ${{ inputs.require_repo_write == 'true' }} |
| 98 | env: |
| 99 | ALLOW_BOTS: ${{ inputs.allow_bots }} |
| 100 | GITHUB_TOKEN: ${{ github.token }} |
| 101 | shell: bash |
| 102 | run: | |
| 103 | node "${{ github.action_path }}/dist/main.js" check-write-access --allow-bots "$ALLOW_BOTS" |
| 104 | |
| 105 | - name: Install Codex CLI |
| 106 | shell: bash |
| 107 | run: npm install -g "@openai/codex@${{ inputs.codex_version }}" |
| 108 | |
| 109 | - name: Install Codex Responses API proxy |
| 110 | shell: bash |
| 111 | run: npm install -g "@openai/codex-responses-api-proxy@${{ inputs.codex_version }}" |
| 112 | |
| 113 | - name: Start Responses API proxy |
| 114 | id: start_proxy |
| 115 | env: |
| 116 | OPENAI_API_KEY: ${{ inputs.openai_api_key }} |
| 117 | shell: bash |
| 118 | run: | |
| 119 | tmpfile=$(mktemp) |
| 120 | ( |
| 121 | printenv OPENAI_API_KEY | codex-responses-api-proxy --http-shutdown --server-info "$tmpfile" |
| 122 | ) & |
| 123 | for _ in {1..10}; do |
| 124 | if [ -s "$tmpfile" ]; then |
| 125 | break |
| 126 | fi |
| 127 | sleep 1 |
| 128 | done |
| 129 | if [ ! -s "$tmpfile" ]; then |
| 130 | echo "responses-api-proxy did not write server info" >&2 |
| 131 | exit 1 |
| 132 | fi |
| 133 | echo "server_info_file=$tmpfile" >> "$GITHUB_OUTPUT" |
| 134 | |
| 135 | # This step has an output named `port`. |
| 136 | - name: Read server info |
| 137 | id: read_server_info |
| 138 | shell: bash |
| 139 | run: node "${{ github.action_path }}/dist/main.js" read-server-info "${{ steps.start_proxy.outputs.server_info_file }}" |
| 140 | |
| 141 | - name: Drop sudo privilege, if appropriate |
| 142 | if: ${{ inputs.safety_strategy == 'drop_sudo' }} |
| 143 | shell: bash |
| 144 | run: | |
| 145 | case "${RUNNER_OS}" in |
| 146 | Linux) |
| 147 | node "${{ github.action_path }}/dist/main.js" drop-sudo --user runner --group sudo |
| 148 | ;; |
| 149 | macOS) |
| 150 | node "${{ github.action_path }}/dist/main.js" drop-sudo --user runner --group admin |
| 151 | ;; |
| 152 | *) |
| 153 | echo "Unsupported OS for drop_sudo: ${RUNNER_OS}" >&2 |
| 154 | exit 1 |
| 155 | ;; |
| 156 | esac |
| 157 | |
| 158 | - name: Verify sudo privilege removed |
| 159 | if: ${{ inputs.safety_strategy == 'drop_sudo' }} |
| 160 | shell: bash |
| 161 | run: | |
| 162 | if sudo -n true 2>/dev/null; then |
| 163 | echo "Expected sudo to be disabled, but sudo succeeded." >&2 |
| 164 | exit 1 |
| 165 | fi |
| 166 | echo "Confirmed sudo privilege is disabled." |
| 167 | |
| 168 | - name: Run codex exec |
| 169 | id: run_codex |
| 170 | env: |
| 171 | CODEX_PROMPT: ${{ inputs.prompt }} |
| 172 | CODEX_PROMPT_FILE: ${{ inputs.prompt_file }} |
| 173 | CODEX_HOME: ${{ inputs.codex_home }} |
| 174 | CODEX_WORKING_DIRECTORY: ${{ inputs.codex_working_directory || github.workspace }} |
| 175 | CODEX_ARGS: ${{ inputs.codex_args }} |
| 176 | CODEX_OUTPUT_FILE: ${{ inputs.codex_output_file }} |
| 177 | CODEX_SAFETY_STRATEGY: ${{ inputs.safety_strategy }} |
| 178 | CODEX_USER: ${{ inputs.codex_user }} |
| 179 | shell: bash |
| 180 | run: | |
| 181 | node "${{ github.action_path }}/dist/main.js" run-codex-exec \ |
| 182 | --prompt "${CODEX_PROMPT}" \ |
| 183 | --prompt-file "${CODEX_PROMPT_FILE}" \ |
| 184 | --codex-home "$CODEX_HOME" \ |
| 185 | --cd "$CODEX_WORKING_DIRECTORY" \ |
| 186 | --proxy-port "${{ steps.read_server_info.outputs.port }}" \ |
| 187 | --extra-args "$CODEX_ARGS" \ |
| 188 | --output-file "$CODEX_OUTPUT_FILE" \ |
| 189 | --safety-strategy "$CODEX_SAFETY_STRATEGY" \ |
| 190 | --codex-user "$CODEX_USER" |
| 191 | |
| 192 | - name: Shutdown codex webserver |
| 193 | env: |
| 194 | CODEX_SERVER_PORT: ${{ steps.read_server_info.outputs.port }} |
| 195 | shell: bash |
| 196 | run: curl --fail --silent --show-error "http://127.0.0.1:${CODEX_SERVER_PORT}/shutdown" |
| 197 | |