openai/codex-action

Public

mirrored from https://github.com/openai/codex-actionAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
pr45

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

action.yml

272lines · modecode

1name: "Codex Exec Action"
2description: "Run `codex exec` with a prompt."
3author: "OpenAI"
4inputs:
5 prompt:
6 description: "Prompt to pass to Codex. `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. `prompt` or `prompt-file` must be provided."
11 required: false
12 default: ""
13 output-file:
14 description: "File where the final Codex message is written. Leave empty to skip writing a file."
15 required: false
16 default: ""
17 openai-api-key:
18 description: "OpenAI API key used by Codex."
19 required: false
20 default: ""
21 working-directory:
22 description: "Working directory that Codex should use. Defaults to the repository root."
23 required: false
24 default: ""
25 sandbox:
26 description: |
27 Sandbox mode for Codex. One of `workspace-write` (default), `read-only` or `danger-full-access`.
28 required: false
29 default: "workspace-write"
30 codex-version:
31 description: "Version of `@openai/codex` to install."
32 required: false
33 default: ""
34 codex-args:
35 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."
36 required: false
37 default: ""
38 output-schema:
39 description: "Inline schema contents to use with `codex exec --output-schema`."
40 required: false
41 default: ""
42 output-schema-file:
43 description: "File path to the schema that should be passed to `codex exec --output-schema`."
44 required: false
45 default: ""
46 model:
47 description: "Model the agent should use."
48 required: false
49 default: ""
50 codex-home:
51 description: "Directory to use as the Codex home directory. If empty, the default Codex home directory will be used."
52 required: false
53 default: ""
54 safety-strategy:
55 description: |
56 Specify one of the following options (on Windows, the only supported option is `unsafe`):
57
58 * `drop-sudo` (default, IRREVERSIBLE) Drop sudo privileges (if any) from
59 the default user before running Codex, and run Codex as that user. This
60 is only supported on Linux and macOS runners. This option is
61 irreversible: if the default user has sudo privileges, they will be
62 removed permanently for the duration of the job.
63 * `unprivileged-user` Run Codex as the specified user specified by the
64 `codex-user` option (the user must already exist). Note the caller is
65 responsible for ensuring the specified user has the privileges it needs
66 to perform the requested actions. For example, the copy of the repo
67 created by `actions/checkout` is not world-readable by default.
68 * `read-only` Run Codex in a sandbox that can read any file on disk,
69 but cannot write to disk or access the network. Note Codex will still
70 run as the default user for this Action, which likely has sudo
71 privileges, so it could read `openai-api-key` from memory and reveal it
72 by printing it to the output of the GitHub Action.
73 * `unsafe` (NOT RECOMMENDED) Do not try to restrict Codex's privileges at all.
74 This is extremely dangerous, as the default user for this Action likely
75 has sudo privileges, which means it can read secrets stored in memory
76 (such as the value of `openai-api-key`) and print them to the output
77 of the GitHub Action or exfiltrate them in other ways.
78 required: false
79 default: "drop-sudo"
80 codex-user:
81 description: "If `safety-strategy` is set to `unprivileged-user`, this specifies the UNIX username to run Codex as."
82 required: false
83 default: ""
84 allow-users:
85 description: "Comma-separated list of GitHub usernames who can run this action, or '*' to allow all users. Note users who have write access to the GitHub repo have access by default and do not need to be listed here."
86 required: false
87 default: ""
88 allow-bots:
89 description: "Allow runs triggered by GitHub Apps/bot accounts to bypass the write-access check."
90 required: false
91 default: "false"
92outputs:
93 final-message:
94 description: "Raw output emitted by `codex exec`."
95 value: ${{ steps.run_codex.outputs['final-message'] }}
96runs:
97 using: "composite"
98 steps:
99 - name: Validate Windows safety strategy
100 if: ${{ runner.os == 'Windows' }}
101 shell: bash
102 run: |
103 if [ "${{ inputs['safety-strategy'] }}" != "unsafe" ]; then
104 echo "On Windows, inputs['safety-strategy'] must be 'unsafe'" >&2
105 echo "because no viable sandboxing options are available at this time." >&2
106 exit 1
107 fi
108
109 - name: Ensure Node.js available
110 # Pin to a commit hash because some repositories require it:
111 # https://github.com/openai/codex-action/issues/43
112 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
113 with:
114 node-version: "20"
115
116 - name: Check repository write access
117 env:
118 GITHUB_TOKEN: ${{ github.token }}
119 shell: bash
120 run: |
121 node "${{ github.action_path }}/dist/main.js" check-write-access \
122 --allow-bots "${{ inputs['allow-bots'] }}" \
123 --allow-users "${{ inputs['allow-users'] }}"
124
125 - name: Install Codex CLI
126 shell: bash
127 run: npm install -g "@openai/codex@${{ inputs['codex-version'] }}"
128
129 - name: Install Codex Responses API proxy
130 shell: bash
131 run: npm install -g "@openai/codex-responses-api-proxy@${{ inputs['codex-version'] }}"
132
133 - name: Resolve Codex home
134 id: resolve_home
135 shell: bash
136 run: |
137 node "${{ github.action_path }}/dist/main.js" resolve-codex-home \
138 --codex-home-override "${{ inputs['codex-home'] }}" \
139 --safety-strategy "${{ inputs['safety-strategy'] }}" \
140 --codex-user "${{ inputs['codex-user'] }}" \
141 --github-run-id "${{ github.run_id }}"
142
143 - name: Determine server info path
144 id: derive_server_info
145 shell: bash
146 run: |
147 server_info_file="${{ steps.resolve_home.outputs.codex-home }}/${{ github.run_id }}.json"
148 echo "server_info_file=$server_info_file" >> "$GITHUB_OUTPUT"
149
150 - name: Check Responses API proxy status
151 id: start_proxy
152 if: ${{ inputs['openai-api-key'] != '' }}
153 shell: bash
154 run: |
155 server_info_file="${{ steps.derive_server_info.outputs.server_info_file }}"
156 if [ -s "$server_info_file" ]; then
157 echo "Responses API proxy already appears to be running (found $server_info_file)."
158 echo "server_info_file_exists=true" >> "$GITHUB_OUTPUT"
159 else
160 echo "server_info_file_exists=false" >> "$GITHUB_OUTPUT"
161 fi
162
163 # This is its own step to minimize the runtime logic that has access to the
164 # API key. Note we use `env -u OPENAI_API_KEY` to ensure extra copies of the
165 # key do not end up in the memory of the `codex-responses-api-proxy` process
166 # where environment variables are stored.
167 - name: Start Responses API proxy
168 if: ${{ inputs['openai-api-key'] != '' && steps.start_proxy.outputs.server_info_file_exists == 'false' }}
169 env:
170 OPENAI_API_KEY: ${{ inputs['openai-api-key'] }}
171 shell: bash
172 run: |
173 (
174 printenv OPENAI_API_KEY | env -u OPENAI_API_KEY codex-responses-api-proxy --http-shutdown --server-info "${{ steps.derive_server_info.outputs.server_info_file }}"
175 ) &
176
177 - name: Wait for Responses API proxy
178 if: ${{ inputs['openai-api-key'] != '' && steps.start_proxy.outputs.server_info_file_exists == 'false' }}
179 shell: bash
180 run: |
181 server_info_file="${{ steps.derive_server_info.outputs.server_info_file }}"
182 for _ in {1..10}; do
183 if [ -s "$server_info_file" ]; then
184 break
185 fi
186 sleep 1
187 done
188 if [ ! -s "$server_info_file" ]; then
189 echo "responses-api-proxy did not write server info" >&2
190 exit 1
191 fi
192
193 if [ "${RUNNER_OS}" != "Windows" ]; then
194 sudo chmod 444 "$server_info_file"
195 sudo chown root "$server_info_file"
196 fi
197
198 # This step has an output named `port`.
199 - name: Read server info
200 id: read_server_info
201 if: ${{ inputs['openai-api-key'] != '' || inputs.prompt != '' || inputs['prompt-file'] != '' }}
202 shell: bash
203 run: node "${{ github.action_path }}/dist/main.js" read-server-info "${{ steps.derive_server_info.outputs.server_info_file }}"
204
205 - name: Write Codex proxy config
206 if: ${{ inputs['openai-api-key'] != '' }}
207 shell: bash
208 run: |
209 node "${{ github.action_path }}/dist/main.js" write-proxy-config \
210 --codex-home "${{ steps.resolve_home.outputs.codex-home }}" \
211 --port "${{ steps.read_server_info.outputs.port }}" \
212 --safety-strategy "${{ inputs['safety-strategy'] }}"
213
214 - name: Drop sudo privilege, if appropriate
215 if: ${{ inputs['safety-strategy'] == 'drop-sudo' && inputs['openai-api-key'] != '' }}
216 shell: bash
217 run: |
218 case "${RUNNER_OS}" in
219 Linux)
220 node "${{ github.action_path }}/dist/main.js" drop-sudo --user runner --group sudo
221 ;;
222 macOS)
223 node "${{ github.action_path }}/dist/main.js" drop-sudo --user runner --group admin
224 ;;
225 *)
226 echo "Unsupported OS for drop-sudo: ${RUNNER_OS}" >&2
227 exit 1
228 ;;
229 esac
230
231 - name: Verify sudo privilege removed
232 if: ${{ inputs['safety-strategy'] == 'drop-sudo' && inputs['openai-api-key'] != '' }}
233 shell: bash
234 run: |
235 if sudo -n true 2>/dev/null; then
236 echo "Expected sudo to be disabled, but sudo succeeded." >&2
237 exit 1
238 fi
239 echo "Confirmed sudo privilege is disabled."
240
241 - name: Run codex exec
242 id: run_codex
243 if: ${{ inputs.prompt != '' || inputs['prompt-file'] != '' }}
244 env:
245 CODEX_PROMPT: ${{ inputs.prompt }}
246 CODEX_PROMPT_FILE: ${{ inputs['prompt-file'] }}
247 CODEX_OUTPUT_FILE: ${{ inputs['output-file'] }}
248 CODEX_HOME: ${{ steps.resolve_home.outputs.codex-home }}
249 CODEX_WORKING_DIRECTORY: ${{ inputs['working-directory'] || github.workspace }}
250 CODEX_SANDBOX: ${{ inputs.sandbox }}
251 CODEX_ARGS: ${{ inputs['codex-args'] }}
252 CODEX_OUTPUT_SCHEMA: ${{ inputs['output-schema'] }}
253 CODEX_OUTPUT_SCHEMA_FILE: ${{ inputs['output-schema-file'] }}
254 CODEX_MODEL: ${{ inputs.model }}
255 CODEX_SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
256 CODEX_USER: ${{ inputs['codex-user'] }}
257 FORCE_COLOR: 1
258 shell: bash
259 run: |
260 node "${{ github.action_path }}/dist/main.js" run-codex-exec \
261 --prompt "${CODEX_PROMPT}" \
262 --prompt-file "${CODEX_PROMPT_FILE}" \
263 --output-file "$CODEX_OUTPUT_FILE" \
264 --codex-home "$CODEX_HOME" \
265 --cd "$CODEX_WORKING_DIRECTORY" \
266 --extra-args "$CODEX_ARGS" \
267 --output-schema "$CODEX_OUTPUT_SCHEMA" \
268 --output-schema-file "$CODEX_OUTPUT_SCHEMA_FILE" \
269 --sandbox "$CODEX_SANDBOX" \
270 --model "$CODEX_MODEL" \
271 --safety-strategy "$CODEX_SAFETY_STRATEGY" \
272 --codex-user "$CODEX_USER"
273