openai/codex-action

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.4

Branches

Tags

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

Clone

HTTPS

Download ZIP

action.yml

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