Summary
When Codex runs through the GitHub Action, text printed during execution can be interpreted as GitHub Actions workflow commands rather than ordinary log output.
This can happen when Codex reads source code containing command syntax such as ##[add-matcher]. The runner attempts to execute the embedded command, potentially marking the step as failed even if Codex itself completes successfully.
This does not require malicious input or an intentional instruction from the model. Simply inspecting a file containing GitHub Actions command syntax can trigger it.
Generic reproduction
The following workflow reproduces the underlying runner behavior without requiring Codex or an API key. It prints source text equivalent to what Codex might emit while inspecting a repository.
name: Reproduce command parsing in logs
on:
workflow_dispatch:
jobs:
reproduction:
runs-on: ubuntu-latest
steps:
- name: Disable workflow commands in an earlier step
run: echo "::stop-commands::repro-$(date +%s)-$RANDOM"
- name: Print source code containing command syntax
id: print_source
continue-on-error: true
run: |
cat <<'EOF'
core.info(`##[add-matcher]${matcherPath}`);
EOF
- name: Check the result
run: |
echo "Step outcome: ${{ steps.print_source.outcome }}"
test "${{ steps.print_source.outcome }}" = "failure"
control:
runs-on: ubuntu-latest
steps:
- name: Suppress commands within the step that prints the text
run: |
token="repro-$(date +%s)-$RANDOM"
echo "::stop-commands::$token"
cat <<'EOF'
core.info(`##[add-matcher]${matcherPath}`);
EOF
echo "::${token}::"
In the reproduction job, the runner interprets ##[add-matcher] inside the printed source text and attempts to load the remaining literal text as a matcher filename. That fails and marks the printing step as failed.
The earlier stop-commands step does not prevent this because suppression does not carry across step boundaries. The control places suppression and output in the same step.
Expected behavior
Codex’s model output and tool output should be treated as log data, not as instructions to the GitHub runner.
A successful Codex invocation should remain successful when it prints source code containing workflow-command syntax. Genuine process failures should still propagate normally.
Actual behavior
Command-like text in Codex’s stdout or stderr can affect the workflow. In particular, a malformed or nonexistent matcher path can fail the step independently of Codex’s exit status.
Downstream steps that require success may consequently be skipped, even when Codex has already produced its final result.
Suggested approach
Protect the output boundary within the action itself, rather than requiring callers to add a preceding suppression step.
One approach is to capture Codex’s stdout/stderr and neutralize workflow-command syntax before forwarding the logs. This should handle:
- Legacy
##[command] syntax embedded anywhere in a line.
- Leading
::command:: syntax.
- Command sequences split across stream chunks.
- Newline and carriage-return boundaries.
- Both stdout and stderr.
The protection should preserve readable logs, leave the final result unchanged, and retain normal exit-status handling.
Summary
When Codex runs through the GitHub Action, text printed during execution can be interpreted as GitHub Actions workflow commands rather than ordinary log output.
This can happen when Codex reads source code containing command syntax such as
##[add-matcher]. The runner attempts to execute the embedded command, potentially marking the step as failed even if Codex itself completes successfully.This does not require malicious input or an intentional instruction from the model. Simply inspecting a file containing GitHub Actions command syntax can trigger it.
Generic reproduction
The following workflow reproduces the underlying runner behavior without requiring Codex or an API key. It prints source text equivalent to what Codex might emit while inspecting a repository.
In the reproduction job, the runner interprets
##[add-matcher]inside the printed source text and attempts to load the remaining literal text as a matcher filename. That fails and marks the printing step as failed.The earlier
stop-commandsstep does not prevent this because suppression does not carry across step boundaries. The control places suppression and output in the same step.Expected behavior
Codex’s model output and tool output should be treated as log data, not as instructions to the GitHub runner.
A successful Codex invocation should remain successful when it prints source code containing workflow-command syntax. Genuine process failures should still propagate normally.
Actual behavior
Command-like text in Codex’s stdout or stderr can affect the workflow. In particular, a malformed or nonexistent matcher path can fail the step independently of Codex’s exit status.
Downstream steps that require success may consequently be skipped, even when Codex has already produced its final result.
Suggested approach
Protect the output boundary within the action itself, rather than requiring callers to add a preceding suppression step.
One approach is to capture Codex’s stdout/stderr and neutralize workflow-command syntax before forwarding the logs. This should handle:
##[command]syntax embedded anywhere in a line.::command::syntax.The protection should preserve readable logs, leave the final result unchanged, and retain normal exit-status handling.