Just shippedBest skills for each job.See the picks →

CodexOtherNot executed· 1 caveat

OpenClaw: docs agent repairs docs after CI passes on main

After CI passes on main, Codex checks whether the docs still match the code and repairs them, at most once an hour. Used in the OpenClaw repository.

after CI succeeds on main (workflow run event) + workflow_dispatch

How it works

From event to result

1
Trigger
GitHub starts a run when another workflow finishes, or you run it by hand.
2
Agent
Codex, via openai/codex-action.
3
What it does
After CI passes on main, Codex checks whether the docs still match the code and repairs them, at most once an hour. Used in the OpenClaw repository.
4
What you get
This listing does not state a pull request or a comment. Read the prompt, and treat the result as not executed.

Setup

Where to paste this

  1. Create .github/workflows/openclaw-docs-agent.yml
  2. Paste the workflow below into that file. The notes beside the YAML are not part of the file.
  3. Add these repository secrets: OPENCLAW_DOCS_AGENT_OPENAI_API_KEY, OPENAI_API_KEY. The file reads them as secrets.NAME. Do not paste the key into the YAML.
  4. Steps that call gh with github.token use the token Actions already provides. You do not create that secret.

Let your agent do it

Copy a prompt for Claude Code, Codex, or Cursor.

Paste it into your coding agent in this repo. It does the setup and asks before anything leaves your machine.

Runs the same job in your agent session, on the plan you are signed in with. No API key, no Action.

Do the job of the "OpenClaw: docs agent repairs docs after CI passes on main" workflow from AgentFlows (https://agentflows.sh/flows/openclaw-docs-agent) right here, in this session, using the plan I'm signed in with. No API key and no GitHub Action.

Install these agent skills first. Download each SKILL.md from its source (respect its license) into .claude/skills/<name>/SKILL.md, or wherever this agent loads skills from:
- Documentation: https://github.com/anthropics/knowledge-work-plugins/blob/main/engineering/skills/documentation/SKILL.md (Apache-2.0), notes at https://agentflows.sh/skills/documentation

What it does: After CI passes on main, Codex checks whether the docs still match the code and repairs them, at most once an hour. Used in the OpenClaw repository.

The workflow normally runs on: after CI succeeds on main (workflow run event) + workflow_dispatch. Instead, ask me which pull request, issue, or branch to work on, then follow these instructions:

```text
After CI passes on main, Codex checks whether the docs still match the code and repairs them, at most once an hour. Used in the OpenClaw repository.
```

Use the gh CLI for anything on GitHub, and show me what you plan to post or change before you do it.

The file

Workflow YAML

Copy the file as-is. The notes beside each block explain it.

.github/workflows/openclaw-docs-agent.yml

Name

The name GitHub shows in the Actions tab. The file path is under Where to paste this.

name: Docs Agent

Trigger

GitHub starts a run when another workflow finishes, or you run it by hand.

on:  workflow_run: # zizmor: ignore[dangerous-triggers] main-only docs repair after trusted CI; job gates repository, event, branch, actor, conclusion, exact current main SHA, and hourly cadence before using write token    workflows:      - CI    types:      - completed  workflow_dispatch:

Permissions

This token may read Actions logs, read the repo, and FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true". Anything not listed is not granted.

permissions:  actions: read  contents: readenv:  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

Jobs

Each job is one runner. The steps inside it run top to bottom.

jobs:  verify-ci:    # Cheap admission keeps default security-only push completions out of cadence    # history. Job evidence below, not the mutable opt-in variable, grants writes.    if: >-      github.repository == 'openclaw/openclaw' &&      ((github.event_name == 'workflow_dispatch' && github.actor != 'github-actions[bot]') ||       (github.event_name == 'workflow_run' &&        github.event.workflow_run.conclusion == 'success' &&        github.event.workflow_run.head_branch == 'main' &&        ((github.event.workflow_run.event == 'push' && vars.OPENCLAW_CI_ON_PUSH == 'true' &&          github.event.workflow_run.actor.login != 'github-actions[bot]') ||         github.event.workflow_run.event == 'schedule')))    runs-on: ubuntu-24.04    timeout-minutes: 5    outputs:      allowed: ${{ steps.verify.outputs.allowed }}

Steps

These steps run in order on that job.

    steps:      - name: Verify successful full CI for current main        id: verify        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9        with:          script: |            core.setOutput("allowed", "false");            if (context.eventName === "workflow_dispatch") {              core.setOutput("allowed", "true");              return;            }            const expected = context.payload.workflow_run;            const { data: run } = await github.rest.actions.getWorkflowRun({              ...context.repo, run_id: expected.id,            });            const repository = `${context.repo.owner}/${context.repo.repo}`;            const push = run.event === "push" && run.actor?.login !== "github-actions[bot]";            const hourly = run.event === "schedule";            if (run.id !== expected.id || !run.actor?.login ||                run.repository.full_name !== repository || run.head_repository.full_name !== repository ||                run.path !== ".github/workflows/ci.yml" || run.head_branch !== "main" ||                run.head_sha !== expected.head_sha || run.run_attempt !== expected.run_attempt ||                run.status !== "completed" || run.conclusion !== "success" || (!push && !hourly)) {              core.info("CI completion does not identify a successful canonical main run.");              return;            }            const { data: main } = await github.rest.repos.getBranch({ ...context.repo, branch: "main" });            if (main.commit.sha !== run.head_sha) {              core.info("CI was superseded by newer main.");              return;            }            const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRunAttempt, {              ...context.repo, run_id: run.id, attempt_number: run.run_attempt, per_page: 100,            });            const gates = jobs.filter((job) => job.name === "openclaw/ci-gate");            if (gates.length !== 1 || gates[0].head_sha !== run.head_sha ||                gates[0].conclusion !== "success" ||                !gates[0].steps.some((step) => step.name === "Confirm validated workflow revision" &&                  step.conclusion === "success")) {              core.info("CI lacks the successful full-validation revision confirmation.");              return;            }            const { data: latest } = await github.rest.actions.getWorkflowRun({              ...context.repo, run_id: run.id,            });            if (latest.run_attempt !== run.run_attempt || latest.head_sha !== run.head_sha ||                latest.status !== "completed" || latest.conclusion !== "success") {              core.info("CI changed while full-validation evidence was being verified.");              return;            }            core.setOutput("allowed", "true");  update-docs:    needs: verify-ci    if: needs.verify-ci.outputs.allowed == 'true'

Permissions

This token may read Actions logs, push commits, group: docs-agent-main, cancel-in-progress: false, runs-on: ubuntu-24.04, and timeout-minutes: 30. Anything not listed is not granted.

    permissions:      actions: read      contents: write    # Only admitted write jobs occupy this slot; skipped push events cannot    # replace pending hourly/manual documentation work.    concurrency:      group: docs-agent-main      cancel-in-progress: false    runs-on: ubuntu-24.04    timeout-minutes: 30

Steps

These steps run in order on that job.

    steps:      - name: Checkout        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1        with:          ref: main          fetch-depth: 0          persist-credentials: false          submodules: false      - name: Prepare Git owner        uses: openclaw/openclaw/.github/actions/git-owner@88095dc00eef288c3829909d4dc64a9e249fa7ef      - name: Gate trusted main activity and hourly cadence        id: gate        env:          EVENT_NAME: ${{ github.event_name }}          GH_TOKEN: ${{ github.token }}          WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}        run: |          set -euo pipefail          remote_main_file="$(mktemp "$RUNNER_TEMP/docs-agent-main.XXXXXX")"          trap 'rm -f -- "$remote_main_file" "$RUNNER_TEMP/docs-agent-runs.json" "$RUNNER_TEMP/docs-agent-history.json"' EXIT          python3 -I -S "$CI_GIT_OWNER" --policy - "$remote_main_file" <<'PYTHON'          import os          import subprocess          import sys          import tempfile          from ci_git_owner import run_git, git_output, GitFailure, FetchTimeout, backoff, check_cancelled          workspace = os.getcwd()          if os.environ["EVENT_NAME"] != "workflow_run":              head_sha = git_output(workspace, "rev-parse", "HEAD").rstrip("\n")              try:                  with tempfile.TemporaryFile(dir=os.environ["RUNNER_TEMP"]) as parent:                      run_git(workspace, "rev-parse", f"{head_sha}^", stdout=parent, stderr=subprocess.DEVNULL)                      parent.seek(0)                      review_base = parent.read().decode().rstrip("\n")              except GitFailure:                  review_base = head_sha              check_cancelled()              with open(os.environ["GITHUB_OUTPUT"], "a") as output:                  output.write(f"run_agent=true\nbase_sha={head_sha}\nreview_base_sha={review_base}\nreview_head_sha={head_sha}\n")              raise SystemExit(0)          for attempt in range(1, 6):              try:                  run_git(workspace, "fetch", "--no-tags", "origin", "main", timeout=120, reclaim_locks=True)                  break              except (GitFailure, FetchTimeout):                  if attempt == 5:                      print("Failed to fetch main after retries.", file=sys.stderr)                      raise SystemExit(1)                  print(f"Fetch attempt {attempt} failed; retrying.", flush=True)                  backoff(attempt * 2)          remote_main = git_output(workspace, "rev-parse", "origin/main").rstrip("\n")          check_cancelled()          if remote_main != os.environ["WORKFLOW_HEAD_SHA"]:              print(f"CI run is superseded by {remote_main}; skipping docs agent for {os.environ['WORKFLOW_HEAD_SHA']}.")              with open(os.environ["GITHUB_OUTPUT"], "a") as output:                  output.write("run_agent=false\n")          else:              # Only a current, fully drained main read permits the shell cadence query.              with open(sys.argv[3], "w") as fact:                  fact.write(remote_main + "\n")          PYTHON          if [ ! -s "$remote_main_file" ]; then            exit 0          fi          remote_main="$(cat "$remote_main_file")"          runs_json="$RUNNER_TEMP/docs-agent-runs.json"          gh api --method GET "repos/${GITHUB_REPOSITORY}/actions/workflows/docs-agent.yml/runs" \            -f branch=main \            -f event=workflow_run \            -f per_page=100 > "$runs_json"          # Workflow success includes denied verification and skipped writer gates.          # Read the exact attempt's writer/agent evidence before using its history.          history_json="$RUNNER_TEMP/docs-agent-history.json"          jq -c --argjson current_run_id "$GITHUB_RUN_ID" \            '.workflow_runs[]              | select(.id != $current_run_id)              | select(.conclusion != "cancelled" and .conclusion != "skipped")' "$runs_json" |            while IFS= read -r run; do              run_id="$(jq -r '.id' <<< "$run")"              run_attempt="$(jq -r '.run_attempt' <<< "$run")"              jobs="$(gh api --paginate --slurp \                "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/attempts/${run_attempt}/jobs?per_page=100")"              jq -n --argjson run "$run" --argjson pages "$jobs" '                [$pages[].jobs[] | select(.name == "update-docs")] as $writers                | select($writers | length == 1)                | $writers[0] as $writer                | [$writer.steps[]? | select(.name == "Run Codex docs agent")] as $reviews                | $run + {                    admitted: ($writer.status == "queued" or $writer.status == "in_progress" or                      any($reviews[]; .status == "completed" and .conclusion != "skipped")),                    reviewed: ($writer.conclusion == "success" and ($reviews | length == 1) and                      $reviews[0].status == "completed" and $reviews[0].conclusion == "success")                  }'            done | jq -s '{workflow_runs: .}' > "$history_json"          runs_json="$history_json"          one_hour_ago="$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)"          recent_runs="$(            jq -r \              --argjson current_run_id "$GITHUB_RUN_ID" \              --arg one_hour_ago "$one_hour_ago" \              '.workflow_runs[]                | select(.id != $current_run_id)                | select(.created_at >= $one_hour_ago)                | select(.admitted)                | [.id, .status, (.conclusion // ""), .created_at, .head_sha]                | @tsv' "$runs_json"          )"          if [ -n "$recent_runs" ]; then            echo "Docs agent already ran or is running within the last hour; skipping."            printf '%s\n' "$recent_runs"            echo "run_agent=false" >> "$GITHUB_OUTPUT"            exit 0          fi          review_base="$(            jq -r \              --argjson current_run_id "$GITHUB_RUN_ID" \              --arg remote_main "$remote_main" \              '.workflow_runs[]                | select(.id != $current_run_id)                | select(.reviewed)                | .head_sha                | select(. != null and . != "")                | select(. != $remote_main)              ' "$runs_json" | head -n 1          )"          python3 -I -S "$CI_GIT_OWNER" --policy - "$remote_main" "$review_base" <<'PYTHON'          import os          import subprocess          import sys          import tempfile          from ci_git_owner import run_git, GitFailure, check_cancelled          remote_main, review_base = sys.argv[3:]          try:              if review_base:                  run_git(os.getcwd(), "cat-file", "-e", f"{review_base}^{{commit}}", stderr=subprocess.DEVNULL)          except GitFailure:              review_base = ""          if not review_base:              try:                  with tempfile.TemporaryFile(dir=os.environ["RUNNER_TEMP"]) as parent:                      run_git(os.getcwd(), "rev-parse", f"{remote_main}^", stdout=parent, stderr=subprocess.DEVNULL)                      parent.seek(0)                      review_base = parent.read().decode().rstrip("\n")              except GitFailure:                  review_base = remote_main          check_cancelled()          with open(os.environ["GITHUB_OUTPUT"], "a") as output:              output.write(f"run_agent=true\nbase_sha={remote_main}\nreview_base_sha={review_base}\nreview_head_sha={remote_main}\n")          PYTHON      - name: Setup Node environment        if: steps.gate.outputs.run_agent == 'true'        uses: ./.github/actions/setup-node-env        with:          cache-mode: restore          install-bun: "false"      - name: Ensure docs agent key exists        if: steps.gate.outputs.run_agent == 'true'        env:          OPENAI_API_KEY: ${{ secrets.OPENCLAW_DOCS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}        run: |          set -euo pipefail          if [ -z "${OPENAI_API_KEY:-}" ]; then            echo "Missing OPENCLAW_DOCS_AGENT_OPENAI_API_KEY or OPENAI_API_KEY secret." >&2            exit 1          fi      - name: Run Codex docs agent        if: steps.gate.outputs.run_agent == 'true'

Agent

Runs Codex with openai/codex-action@bdf19a4a223ec2549a3e2274a0cf61556bc07675. Inputs: openai-api-key (the OpenAI API key secret); prompt-file; model; effort; sandbox; safety-strategy.

        uses: openai/codex-action@bdf19a4a223ec2549a3e2274a0cf61556bc07675 # v1.13        env:          DOCS_AGENT_BASE_SHA: ${{ steps.gate.outputs.review_base_sha }}          DOCS_AGENT_HEAD_SHA: ${{ steps.gate.outputs.review_head_sha }}        with:          openai-api-key: ${{ secrets.OPENCLAW_DOCS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}          prompt-file: .github/codex/prompts/docs-agent.md          model: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}          effort: medium          sandbox: workspace-write          safety-strategy: drop-sudo      - name: Enforce existing-docs-only patch        if: steps.gate.outputs.run_agent == 'true'        run: |          set -euo pipefail          untracked="$(python3 -I -S "$CI_GIT_OWNER" --checkout-git 0 ls-files --others --exclude-standard)"          if [ -n "$untracked" ]; then            echo "Docs agent created untracked files; forbidden:"            printf '%s\n' "$untracked"            exit 1          fi          added_or_deleted="$(            python3 -I -S "$CI_GIT_OWNER" --checkout-git 0 diff HEAD --name-status --diff-filter=AD || exit "$?"            python3 -I -S "$CI_GIT_OWNER" --checkout-git 0 diff --cached HEAD --name-status --diff-filter=AD || exit "$?"          )"          if [ -n "$added_or_deleted" ]; then            echo "Docs agent added or deleted tracked files; forbidden:"            printf '%s\n' "$added_or_deleted"            exit 1          fi          filter_bad_paths() {            while IFS= read -r path; do              case "$path" in                docs/*|README.md) ;;                CHANGELOG/*.md)                  # Only regenerate an existing mirror from its original ordered sources.                  if ! python3 -I -S "$CI_GIT_OWNER" --checkout-git 0 show "HEAD:$path" |                    node --input-type=module -e '                      import { readFileSync } from "node:fs";                      import { changelogEntryPath } from "./scripts/lib/release-changelog.mjs";                      import { parseReleaseDocsMirror, renderReleaseDocsMirror } from "./scripts/lib/release-docs-mirror.mjs";                      const file = process.argv[1];                      const metadata = parseReleaseDocsMirror(readFileSync(0, "utf8"));                      if (!metadata || file !== changelogEntryPath(metadata.version) ||                          readFileSync(file, "utf8") !== renderReleaseDocsMirror({ rootDir: process.cwd(), ...metadata })) {                        throw new Error("Docs agent may only regenerate an existing docs mirror");                      }                    ' "$path"                  then                    printf '%s\n' "$path"                  fi                  ;;                *) printf '%s\n' "$path" ;;              esac            done          }          # A restored working copy can still hide a forbidden edit in the index.          bad_paths="$(            {              python3 -I -S "$CI_GIT_OWNER" --checkout-git 0 diff HEAD --name-only || exit "$?"              python3 -I -S "$CI_GIT_OWNER" --checkout-git 0 diff --cached HEAD --name-only || exit "$?"            } | sort -u | filter_bad_paths          )"          if [ -n "$bad_paths" ]; then            echo "Docs agent touched non-doc paths; forbidden:"            printf '%s\n' "$bad_paths"            exit 1          fi      - name: Restore Node 24 path        if: steps.gate.outputs.run_agent == 'true'        run: | # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job          set -euo pipefail          export PATH="${NODE_BIN}:${PATH}"          echo "${NODE_BIN}" >> "$GITHUB_PATH"          node -v          corepack enable          pnpm -v      - name: Check docs        if: steps.gate.outputs.run_agent == 'true'        run: pnpm check:docs      - name: Commit docs updates        if: steps.gate.outputs.run_agent == 'true'        env:          BASE_SHA: ${{ steps.gate.outputs.base_sha }}          GITHUB_TOKEN: ${{ github.token }}          TARGET_BRANCH: main        run: |          set -euo pipefail          exec python3 -I -S "$CI_GIT_OWNER" --policy - <<'PYTHON'          import os          import sys          from ci_git_owner import run_git, git_output, GitFailure, FetchTimeout, backoff          workspace = os.getcwd()          target = os.environ["TARGET_BRANCH"]          base_sha = os.environ["BASE_SHA"]          try:              run_git(workspace, "diff", "HEAD", "--quiet")          except GitFailure:              pass          else:              print("No docs changes.")              raise SystemExit(0)          run_git(workspace, "config", "user.name", "openclaw-docs-agent[bot]", reclaim_locks=True)          run_git(workspace, "config", "user.email", "openclaw-docs-agent[bot]@users.noreply.github.com", reclaim_locks=True)          run_git(workspace, "add", "docs", "README.md", "CHANGELOG", reclaim_locks=True)          run_git(workspace, "commit", "--no-verify", "-m", "docs: refresh documentation", reclaim_locks=True)          for attempt in range(1, 6):              try:                  run_git(workspace, "fetch", "--no-tags", "origin", target, timeout=120, reclaim_locks=True)              except (GitFailure, FetchTimeout):                  print(f"Fetch attempt {attempt} failed; retrying.", flush=True)                  backoff(attempt * 2)                  continue              try:                  run_git(workspace, "push",                          f"https://x-access-token:{os.environ['GITHUB_TOKEN']}@github.com/{os.environ['GITHUB_REPOSITORY']}.git",                          f"HEAD:{target}", reclaim_locks=True)                  raise SystemExit(0)              except GitFailure:                  # A push failure permits stale/retry decisions only after verified cleanup.                  remote_main = git_output(workspace, "rev-parse", f"origin/{target}").rstrip("\n")                  if remote_main != base_sha:                      print(f"main advanced from {base_sha} to {remote_main}; skipping stale docs update.")                      raise SystemExit(0)                  print(f"Docs update attempt {attempt} failed; retrying.", flush=True)                  backoff(attempt * 2)          print("Failed to push docs updates after retries.", file=sys.stderr)          raise SystemExit(1)          PYTHON

Keep going

More workflows

All workflows
  • CodexPull request
    12

    Codex PR review posted as a comment

    Two-job split keeps the API key job read-only (README example pattern). `:read-only` profile per README Permission profiles.

    pull_request: opened

    Est. cost per run~$0.03 runner + OpenAI tokens (UNVERIFIED)Checked Oct 16, 2026

    Not executed· 3 caveats
  • CodexLabel
    13

    Codex implements an issue on label

    `:workspace` profile lets Codex edit the checkout (README). PR creation is plain git/gh, not an action input.

    issues: labeled == codex

    Est. cost per run~$0.09 runner + OpenAI tokens (UNVERIFIED)Checked Oct 16, 2026

    Not executed· 2 caveats
  • CodexIssue opened
    14

    Codex triage with JSON output schema

    `output-schema` is forwarded to `codex exec --output-schema` (README inputs table).

    issues: opened

    Est. cost per run~$0.01 runner + OpenAI tokens (UNVERIFIED)Checked Oct 16, 2026

    Not executed· 3 caveats