openai/codex-action

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
pr46

Branches

Tags

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

Clone

HTTPS

Download ZIP

action.yml

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