Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 72 additions & 9 deletions .github/workflows/claude-auto-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -535,17 +535,67 @@ jobs:
.pytest_cache/ .mypy_cache/ .ruff_cache/ .coverage node_modules/ \
>>.git/info/exclude

# codex-action's default safety-strategy (drop-sudo) chmods root-owned
# service sockets under /run, breaking D-Bus -> systemd-resolved -> DNS;
# the hosted runner then dies with "lost communication" 52-65 minutes
# into the job (openai/codex-action#160; observed twice at ~62 min on
# inspect_ai#389, 2026-08-31). Until fixed upstream, run codex as a
# dedicated unprivileged user — the README's other blessed strategy for
# permission profiles: containment is the user boundary plus the
# profile sandbox, and the API key stays out of reach (codex has no
# sudo). Setup mirrors the action's examples/unprivileged-user.yml —
# the chown/g+rwX is what lets codex (primary group codex) write the
# runner-owned workspace — plus two grants the example never needs (it
# passes no explicit output-file and its demo prompt never runs git):
# a codex-owned output dir under RUNNER_TEMP and git safe.directory,
# commented inline.
# NB the runner->codex group grant is inert for this job:
# supplementary groups are fixed at process start, and every step is
# spawned from a Runner.Worker that predates the usermod. Landing
# codex's edits works anyway — runner still owns the pre-existing
# workspace directories, codex's files are world-readable under its
# umask, and the land step reclaims .git — the one place codex-owned
# dirs (objects/ fan-out) would block runner's writes. (Group kept as
# a faithful copy of the example; if codex ever drops a 600 file, the
# land step needs a chmod -R a+rX sweep, not this group.)
- name: Create codex user
id: codexuser
if: steps.gate.outputs.act == 'fix' && steps.gate.outputs.engine == 'codex'
run: |
set -euo pipefail
sudo adduser --system --home /home/codex --shell /bin/bash --group codex
sudo usermod -a -G codex runner
sudo usermod -a -G runner codex
sudo chown -R runner:codex "$GITHUB_WORKSPACE"
sudo chmod -R g+rwX "$GITHUB_WORKSPACE"
sudo find "$GITHUB_WORKSPACE" -type d -exec chmod g+s {} +
# codex itself (via sudo -u) writes the explicit output-file and
# the action re-reads it as codex, but RUNNER_TEMP is 755
# runner:runner on the hosted image. Not g+w on the temp root:
# codex's runner-group membership IS effective (its processes
# start post-usermod), so that would hand it rename/delete over
# the runner's step scripts and the per-step GITHUB_ENV/OUTPUT/
# PATH files. A codex-owned subdir grants only the output-file
# write; 755 keeps $OUT readable for the runner-side land step.
sudo install -d -o codex -g codex -m 755 "$RUNNER_TEMP/codex"
# The checkout stays runner-owned, so git commands running as
# codex hit the dubious-ownership refusal, and no profile sandbox
# lets the agent add the exemption itself.
sudo -u codex -H git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Run Codex fix
id: codexfix
if: steps.gate.outputs.act == 'fix' && steps.gate.outputs.engine == 'codex'
uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
safety-strategy: unprivileged-user
codex-user: codex
model: ${{ inputs.codex_model }}
permission-profile: ":workspace"
allow-bot-users: "claude"
prompt-file: .codex-prompt.md
output-file: ${{ runner.temp }}/codex-final.md
output-file: ${{ runner.temp }}/codex/codex-final.md

- name: Land codex fix
id: codexland
Expand All @@ -556,11 +606,18 @@ jobs:
PR: ${{ inputs.pr_number }}
ROUND: ${{ steps.gate.outputs.round }}
MENTION: ${{ steps.gate.outputs.mention }}
OUT: ${{ runner.temp }}/codex-final.md
OUT: ${{ runner.temp }}/codex/codex-final.md
START_SHA: ${{ steps.base.outputs.sha }}
HEAD_BRANCH: ${{ steps.gate.outputs.head_branch }}
run: |
set -euo pipefail
# Reclaim .git before writing objects as runner: any objects/XX
# fan-out dir codex created (any object-writing git command) is
# codex-owned, and runner's codex-group membership is inert this
# job — the commit below (or the hand-back backstop's fetch)
# would intermittently fail with "insufficient permission for
# adding an object".
sudo chown -R runner "$GITHUB_WORKSPACE/.git"
if [ -n "$(git status --porcelain)" ]; then
# || true inside the substitution: without it a missing/unreadable
# $OUT fails the pipeline and set -e kills the step before the
Expand Down Expand Up @@ -723,6 +780,7 @@ jobs:
MENTION: ${{ steps.gate.outputs.mention }}
HANDED: ${{ steps.selfhandoff.outputs.found }}
CODEXPREP_OUTCOME: ${{ steps.codexprep.outcome }}
CODEXUSER_OUTCOME: ${{ steps.codexuser.outcome }}
CODEX_OUTCOME: ${{ steps.codexfix.outcome }}
CODEXLAND_OUTCOME: ${{ steps.codexland.outcome }}
run: |
Expand All @@ -733,6 +791,11 @@ jobs:
if [ -z "$err" ] && [ "${CODEXPREP_OUTCOME:-}" = "failure" ]; then
err="the codex context-preparation step failed before the agent started — no fix was attempted this round (see the job logs)."
fi
# A failed codex-user setup SKIPS the codex step, so it needs its
# own check or the failure is a silent red job.
if [ -z "$err" ] && [ "${CODEXUSER_OUTCOME:-}" = "failure" ]; then
err="the codex user setup failed before the agent started — no fix was attempted this round (a runner-image change breaking adduser/usermod? — see the job logs)."
fi
if [ -z "$err" ] && [ "${CODEX_OUTCOME:-}" = "failure" ]; then
err="the codex fix step failed (missing OPENAI_API_KEY, or a codex-action error — see the job logs)."
fi
Expand Down Expand Up @@ -798,17 +861,17 @@ jobs:
# nothing. A failed codex step is the same class — its landing step
# never ran, so nothing reached the PR (persistently missing
# OPENAI_API_KEY must not march a PR to spurious escalation) — and so
# is a failed codex PREP step, which dies before the agent starts and
# skips codexfix outright; the exec-file guard below is Claude-only and
# no execution file ever exists on the codex path, so the refund
# proceeds. Rebuild the sticky counter body including the head marker
# the next gate's no-progress check reads. Per-PR concurrency
# serializes us.
# are a failed codex PREP or user-setup step, which die before the
# agent starts and skip codexfix outright; the exec-file guard below is
# Claude-only and no execution file ever exists on the codex path, so
# the refund proceeds. Rebuild the sticky counter body including the
# head marker the next gate's no-progress check reads. Per-PR
# concurrency serializes us.
- name: Refund infra-crashed round
if: >-
always() && steps.gate.outputs.act == 'fix' &&
(steps.claude.outcome == 'failure' || steps.codexfix.outcome == 'failure' ||
steps.codexprep.outcome == 'failure')
steps.codexprep.outcome == 'failure' || steps.codexuser.outcome == 'failure')
env:
GH_TOKEN: ${{ secrets.MARVIN_TOKEN || github.token }}
REPO: ${{ github.repository }}
Expand Down
77 changes: 70 additions & 7 deletions .github/workflows/claude-auto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,62 @@ jobs:
.pytest_cache/ .mypy_cache/ .ruff_cache/ .coverage node_modules/ \
>>.git/info/exclude

# codex-action's default safety-strategy (drop-sudo) chmods root-owned
# service sockets under /run, breaking D-Bus -> systemd-resolved -> DNS;
# the hosted runner then dies with "lost communication" 52-65 minutes
# into the job (openai/codex-action#160; observed twice at ~62 min on
# inspect_ai#389, 2026-08-31). Until fixed upstream, run codex as a
# dedicated unprivileged user — the README's other blessed strategy for
# permission profiles: containment is the user boundary plus the
# profile sandbox, and the API key stays out of reach (codex has no
# sudo). Setup mirrors the action's examples/unprivileged-user.yml —
# the chown/g+rwX is what lets codex (primary group codex) write the
# runner-owned workspace — plus two grants the example never needs (it
# passes no explicit output-file and its demo prompt never runs git):
# a codex-owned output dir under RUNNER_TEMP and git safe.directory,
# commented inline.
# NB the runner->codex group grant is inert for this job:
# supplementary groups are fixed at process start, and every step is
# spawned from a Runner.Worker that predates the usermod. Landing
# codex's edits works anyway — runner still owns the pre-existing
# workspace directories, codex's files are world-readable under its
# umask, and the land step reclaims .git — the one place codex-owned
# dirs (objects/ fan-out) would block runner's writes. (Group kept as
# a faithful copy of the example; if codex ever drops a 600 file, the
# land step needs a chmod -R a+rX sweep, not this group.)
- name: Create codex user
id: codexuser
if: steps.gate.outputs.act == 'fix' && steps.gate.outputs.engine == 'codex'
run: |
set -euo pipefail
sudo adduser --system --home /home/codex --shell /bin/bash --group codex
sudo usermod -a -G codex runner
sudo usermod -a -G runner codex
sudo chown -R runner:codex "$GITHUB_WORKSPACE"
sudo chmod -R g+rwX "$GITHUB_WORKSPACE"
sudo find "$GITHUB_WORKSPACE" -type d -exec chmod g+s {} +
# codex itself (via sudo -u) writes the explicit output-file and
# the action re-reads it as codex, but RUNNER_TEMP is 755
# runner:runner on the hosted image. Not g+w on the temp root:
# codex's runner-group membership IS effective (its processes
# start post-usermod), so that would hand it rename/delete over
# the runner's step scripts and the per-step GITHUB_ENV/OUTPUT/
# PATH files. A codex-owned subdir grants only the output-file
# write; 755 keeps $OUT readable for the runner-side land step.
sudo install -d -o codex -g codex -m 755 "$RUNNER_TEMP/codex"
# The checkout stays runner-owned, so git commands running as
# codex hit the dubious-ownership refusal, and no profile sandbox
# lets the agent add the exemption itself.
sudo -u codex -H git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Run Codex CI fix
id: codexfix
if: steps.gate.outputs.act == 'fix' && steps.gate.outputs.engine == 'codex'
uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
safety-strategy: unprivileged-user
codex-user: codex
model: ${{ inputs.codex_model }}
permission-profile: ":workspace"
# workflow_run's actor is the CI run's actor: normally the machine
Expand All @@ -372,7 +422,7 @@ jobs:
# other codex steps.
allow-bot-users: "claude"
prompt-file: .codex-prompt.md
output-file: ${{ runner.temp }}/codex-final.md
output-file: ${{ runner.temp }}/codex/codex-final.md

- name: Land codex fix
id: codexland
Expand All @@ -382,11 +432,18 @@ jobs:
REPO: ${{ github.repository }}
PR: ${{ steps.gate.outputs.pr }}
ATTEMPT: ${{ steps.gate.outputs.attempt }}
OUT: ${{ runner.temp }}/codex-final.md
OUT: ${{ runner.temp }}/codex/codex-final.md
START_SHA: ${{ steps.base.outputs.sha }}
HEAD_BRANCH: ${{ inputs.head_branch }}
run: |
set -euo pipefail
# Reclaim .git before writing objects as runner: any objects/XX
# fan-out dir codex created (any object-writing git command) is
# codex-owned, and runner's codex-group membership is inert this
# job — the commit below (or the hand-back backstop's fetch)
# would intermittently fail with "insufficient permission for
# adding an object".
sudo chown -R runner "$GITHUB_WORKSPACE/.git"
if [ -n "$(git status --porcelain)" ]; then
# || true inside the substitution: without it a missing/unreadable
# $OUT fails the pipeline and set -e kills the step before the
Expand Down Expand Up @@ -510,6 +567,7 @@ jobs:
BASE_SHA: ${{ steps.base.outputs.sha }}
SINCE: ${{ steps.base.outputs.ts }}
CODEXPREP_OUTCOME: ${{ steps.codexprep.outcome }}
CODEXUSER_OUTCOME: ${{ steps.codexuser.outcome }}
CODEX_OUTCOME: ${{ steps.codexfix.outcome }}
CODEXLAND_OUTCOME: ${{ steps.codexland.outcome }}
run: |
Expand All @@ -520,6 +578,11 @@ jobs:
if [ -z "$err" ] && [ "${CODEXPREP_OUTCOME:-}" = "failure" ]; then
err="the codex context-preparation step failed before the agent started — no fix was attempted (see the job logs)."
fi
# A failed codex-user setup SKIPS the codex step, so it needs its
# own check or the failure is a silent red job.
if [ -z "$err" ] && [ "${CODEXUSER_OUTCOME:-}" = "failure" ]; then
err="the codex user setup failed before the agent started — no fix was attempted (a runner-image change breaking adduser/usermod? — see the job logs)."
fi
if [ -z "$err" ] && [ "${CODEX_OUTCOME:-}" = "failure" ]; then
err="the codex fix step failed (missing OPENAI_API_KEY, or a codex-action error — see the job logs)."
fi
Expand Down Expand Up @@ -628,16 +691,16 @@ jobs:
# counter back one; any run that produced execution output still counts.
# A failed codex step is the same class — its landing step never ran,
# so no fix reached the branch (a persistently missing OPENAI_API_KEY
# must not burn attempts toward spurious escalation) — and so is a
# failed codex PREP step, which dies before the agent starts and skips
# codexfix outright; no execution file ever exists on the codex path,
# so the guard below refunds it.
# must not burn attempts toward spurious escalation) — and so are a
# failed codex PREP or user-setup step, which die before the agent
# starts and skip codexfix outright; no execution file ever exists on
# the codex path, so the guard below refunds it.
# Safe from races: the job's per-PR concurrency group serializes us.
- name: Refund infra-crashed attempt
if: >-
always() && steps.gate.outputs.act == 'fix' &&
(steps.claude.outcome == 'failure' || steps.codexfix.outcome == 'failure' ||
steps.codexprep.outcome == 'failure')
steps.codexprep.outcome == 'failure' || steps.codexuser.outcome == 'failure')
env:
GH_TOKEN: ${{ secrets.MARVIN_TOKEN || github.token }}
REPO: ${{ github.repository }}
Expand Down
Loading