openai/codex-action

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

action.yml

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