Skip to content

Codex output containing GitHub workflow commands can fail an otherwise successful action #175

Description

@jestrada

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions