openai/codex-action

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.5

Branches

Tags

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

Clone

HTTPS

Download ZIP

action.yml

325lines · 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 env:
111 SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
112 run: |
113 if [ "$SAFETY_STRATEGY" != "unsafe" ]; then
114 echo "On Windows, inputs['safety-strategy'] must be 'unsafe'" >&2
115 echo "because no viable sandboxing options are available at this time." >&2
116 exit 1
117 fi
118
119 - name: Ensure Node.js available
120 # Pin to a commit hash because some repositories require it:
121 # https://github.com/openai/codex-action/issues/43
122 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
123 with:
124 node-version: "20"
125
126 - name: Check repository write access
127 env:
128 ACTION_PATH: ${{ github.action_path }}
129 GITHUB_TOKEN: ${{ github.token }}
130 ALLOW_BOTS: ${{ inputs['allow-bots'] }}
131 ALLOW_USERS: ${{ inputs['allow-users'] }}
132 shell: bash
133 run: |
134 node "$ACTION_PATH/dist/main.js" check-write-access \
135 --allow-bots "$ALLOW_BOTS" \
136 --allow-users "$ALLOW_USERS"
137
138 - name: Install Codex CLI
139 shell: bash
140 env:
141 CODEX_VERSION: ${{ inputs['codex-version'] }}
142 run: npm install -g "@openai/codex@${CODEX_VERSION}"
143
144 - name: Install Codex Responses API proxy
145 shell: bash
146 env:
147 CODEX_VERSION: ${{ inputs['codex-version'] }}
148 run: npm install -g "@openai/codex-responses-api-proxy@${CODEX_VERSION}"
149
150 - name: Resolve Codex home
151 id: resolve_home
152 shell: bash
153 env:
154 ACTION_PATH: ${{ github.action_path }}
155 CODEX_HOME_OVERRIDE: ${{ inputs['codex-home'] }}
156 SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
157 CODEX_USER: ${{ inputs['codex-user'] }}
158 CODEX_RUN_ID: ${{ github.run_id }}
159 run: |
160 node "$ACTION_PATH/dist/main.js" resolve-codex-home \
161 --codex-home-override "$CODEX_HOME_OVERRIDE" \
162 --safety-strategy "$SAFETY_STRATEGY" \
163 --codex-user "$CODEX_USER" \
164 --github-run-id "$CODEX_RUN_ID"
165
166 - name: Determine server info path
167 id: derive_server_info
168 shell: bash
169 env:
170 CODEX_HOME: ${{ steps.resolve_home.outputs.codex-home }}
171 CODEX_RUN_ID: ${{ github.run_id }}
172 run: |
173 server_info_file="$CODEX_HOME/$CODEX_RUN_ID.json"
174 echo "server_info_file=$server_info_file" >> "$GITHUB_OUTPUT"
175
176 - name: Check Responses API proxy status
177 id: start_proxy
178 if: ${{ inputs['openai-api-key'] != '' }}
179 shell: bash
180 env:
181 SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
182 run: |
183 if [ -s "$SERVER_INFO_FILE" ]; then
184 echo "Responses API proxy already appears to be running (found $SERVER_INFO_FILE)."
185 echo "server_info_file_exists=true" >> "$GITHUB_OUTPUT"
186 else
187 echo "server_info_file_exists=false" >> "$GITHUB_OUTPUT"
188 fi
189
190 # This is its own step to minimize the runtime logic that has access to the
191 # API key. Note we use `env -u PROXY_API_KEY` to ensure extra copies of the
192 # key do not end up in the memory of the `codex-responses-api-proxy`
193 # process where environment variables are stored.
194 - name: Start Responses API proxy
195 if: ${{ inputs['openai-api-key'] != '' && steps.start_proxy.outputs.server_info_file_exists == 'false' }}
196 env:
197 SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
198 PROXY_API_KEY: ${{ inputs['openai-api-key'] }}
199 UPSTREAM_URL: ${{ inputs['responses-api-endpoint'] }}
200 shell: bash
201 run: |
202 args=(
203 codex-responses-api-proxy
204 --http-shutdown
205 --server-info "$SERVER_INFO_FILE"
206 )
207
208 if [ -n "$UPSTREAM_URL" ]; then
209 args+=(--upstream-url "$UPSTREAM_URL")
210 fi
211
212 (
213 printenv PROXY_API_KEY | env -u PROXY_API_KEY "${args[@]}"
214 ) &
215
216 - name: Wait for Responses API proxy
217 if: ${{ inputs['openai-api-key'] != '' && steps.start_proxy.outputs.server_info_file_exists == 'false' }}
218 shell: bash
219 env:
220 SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
221 run: |
222 for _ in {1..10}; do
223 if [ -s "$SERVER_INFO_FILE" ]; then
224 break
225 fi
226 sleep 1
227 done
228 if [ ! -s "$SERVER_INFO_FILE" ]; then
229 echo "responses-api-proxy did not write server info" >&2
230 exit 1
231 fi
232
233 if [ "${RUNNER_OS}" != "Windows" ]; then
234 sudo chmod 444 "$SERVER_INFO_FILE"
235 sudo chown root "$SERVER_INFO_FILE"
236 fi
237
238 # This step has an output named `port`.
239 - name: Read server info
240 id: read_server_info
241 if: ${{ inputs['openai-api-key'] != '' || inputs.prompt != '' || inputs['prompt-file'] != '' }}
242 shell: bash
243 env:
244 ACTION_PATH: ${{ github.action_path }}
245 SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
246 run: node "$ACTION_PATH/dist/main.js" read-server-info "$SERVER_INFO_FILE"
247
248 - name: Write Codex proxy config
249 if: ${{ inputs['openai-api-key'] != '' }}
250 shell: bash
251 env:
252 ACTION_PATH: ${{ github.action_path }}
253 CODEX_HOME: ${{ steps.resolve_home.outputs.codex-home }}
254 PROXY_PORT: ${{ steps.read_server_info.outputs.port }}
255 SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
256 run: |
257 node "$ACTION_PATH/dist/main.js" write-proxy-config \
258 --codex-home "$CODEX_HOME" \
259 --port "$PROXY_PORT" \
260 --safety-strategy "$SAFETY_STRATEGY"
261
262 - name: Drop sudo privilege, if appropriate
263 if: ${{ inputs['safety-strategy'] == 'drop-sudo' && inputs['openai-api-key'] != '' }}
264 shell: bash
265 env:
266 ACTION_PATH: ${{ github.action_path }}
267 run: |
268 case "${RUNNER_OS}" in
269 Linux)
270 node "$ACTION_PATH/dist/main.js" drop-sudo --user runner --group sudo
271 ;;
272 macOS)
273 node "$ACTION_PATH/dist/main.js" drop-sudo --user runner --group admin
274 ;;
275 *)
276 echo "Unsupported OS for drop-sudo: ${RUNNER_OS}" >&2
277 exit 1
278 ;;
279 esac
280
281 - name: Verify sudo privilege removed
282 if: ${{ inputs['safety-strategy'] == 'drop-sudo' && inputs['openai-api-key'] != '' }}
283 shell: bash
284 run: |
285 if sudo -n true 2>/dev/null; then
286 echo "Expected sudo to be disabled, but sudo succeeded." >&2
287 exit 1
288 fi
289 echo "Confirmed sudo privilege is disabled."
290
291 - name: Run codex exec
292 id: run_codex
293 if: ${{ inputs.prompt != '' || inputs['prompt-file'] != '' }}
294 env:
295 CODEX_PROMPT: ${{ inputs.prompt }}
296 CODEX_PROMPT_FILE: ${{ inputs['prompt-file'] }}
297 CODEX_OUTPUT_FILE: ${{ inputs['output-file'] }}
298 CODEX_HOME: ${{ steps.resolve_home.outputs.codex-home }}
299 CODEX_WORKING_DIRECTORY: ${{ inputs['working-directory'] || github.workspace }}
300 CODEX_SANDBOX: ${{ inputs.sandbox }}
301 CODEX_ARGS: ${{ inputs['codex-args'] }}
302 CODEX_OUTPUT_SCHEMA: ${{ inputs['output-schema'] }}
303 CODEX_OUTPUT_SCHEMA_FILE: ${{ inputs['output-schema-file'] }}
304 CODEX_MODEL: ${{ inputs.model }}
305 CODEX_EFFORT: ${{ inputs.effort }}
306 CODEX_SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
307 CODEX_USER: ${{ inputs['codex-user'] }}
308 ACTION_PATH: ${{ github.action_path }}
309 FORCE_COLOR: 1
310 shell: bash
311 run: |
312 node "$ACTION_PATH/dist/main.js" run-codex-exec \
313 --prompt "${CODEX_PROMPT}" \
314 --prompt-file "${CODEX_PROMPT_FILE}" \
315 --output-file "$CODEX_OUTPUT_FILE" \
316 --codex-home "$CODEX_HOME" \
317 --cd "$CODEX_WORKING_DIRECTORY" \
318 --extra-args "$CODEX_ARGS" \
319 --output-schema "$CODEX_OUTPUT_SCHEMA" \
320 --output-schema-file "$CODEX_OUTPUT_SCHEMA_FILE" \
321 --sandbox "$CODEX_SANDBOX" \
322 --model "$CODEX_MODEL" \
323 --effort "$CODEX_EFFORT" \
324 --safety-strategy "$CODEX_SAFETY_STRATEGY" \
325 --codex-user "$CODEX_USER"
326