From 252e4dde392ae43aa2dc560d5397562fa3e0f807 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 23 Apr 2026 08:22:40 +0100 Subject: [PATCH] ci: add docs agent workflow --- .github/codex/prompts/docs-agent.md | 31 +++++ .github/workflows/docs-agent.yml | 183 ++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 .github/codex/prompts/docs-agent.md create mode 100644 .github/workflows/docs-agent.yml diff --git a/.github/codex/prompts/docs-agent.md b/.github/codex/prompts/docs-agent.md new file mode 100644 index 00000000000..3331b199d49 --- /dev/null +++ b/.github/codex/prompts/docs-agent.md @@ -0,0 +1,31 @@ +# OpenClaw Docs Agent + +You are maintaining OpenClaw documentation after a main-branch commit. + +Goal: inspect the code changes and existing documentation, then update existing docs only when they are stale, incomplete, or misleading. + +Hard limits: + +- Edit existing files only. +- Do not create new docs pages, images, assets, scripts, code files, or workflow files. +- Do not delete or rename files. +- Do not change production code, tests, package metadata, generated baselines, lockfiles, or CI config. +- Keep changes minimal and factual. +- Use "plugin/plugins" in user-facing docs/UI/changelog; `extensions/` is only the internal workspace layout. +- Do not add a changelog entry unless the docs update describes a user-facing behavior/API change from the triggering commit. + +Allowed paths: + +- `docs/**` +- `README.md` +- `CHANGELOG.md` + +Required workflow: + +1. Run `pnpm docs:list` if available and read relevant docs based on `read_when` hints. +2. Inspect the triggering event via `$GITHUB_EVENT_PATH`, then review the relevant commit range and changed files. +3. Update stale existing documentation, if needed. +4. Run `pnpm check:docs` if dependencies are available. +5. Leave the worktree clean if no docs need changes. + +When uncertain, prefer no edit and explain the uncertainty in the final message. diff --git a/.github/workflows/docs-agent.yml b/.github/workflows/docs-agent.yml new file mode 100644 index 00000000000..cf19ab2e5e6 --- /dev/null +++ b/.github/workflows/docs-agent.yml @@ -0,0 +1,183 @@ +name: Docs Agent + +on: + workflow_run: + workflows: + - CI + types: + - completed + schedule: + - cron: "17 5 * * *" + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: docs-agent-main + cancel-in-progress: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + +jobs: + update-docs: + if: > + github.repository == 'openclaw/openclaw' && + github.actor != 'github-actions[bot]' && + (github.event_name != 'workflow_run' || + (github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' && + github.event.workflow_run.actor.login != 'github-actions[bot]')) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + persist-credentials: false + submodules: false + + - name: Skip superseded workflow runs + id: superseded + env: + EVENT_NAME: ${{ github.event_name }} + WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + set -euo pipefail + + if [ "$EVENT_NAME" != "workflow_run" ]; then + echo "run_agent=true" >> "$GITHUB_OUTPUT" + echo "base_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git fetch --no-tags origin main + remote_main="$(git rev-parse origin/main)" + if [ "$remote_main" != "$WORKFLOW_HEAD_SHA" ]; then + echo "CI run is superseded by ${remote_main}; skipping docs agent for ${WORKFLOW_HEAD_SHA}." + echo "run_agent=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "run_agent=true" >> "$GITHUB_OUTPUT" + echo "base_sha=${remote_main}" >> "$GITHUB_OUTPUT" + + - name: Setup Node environment + if: steps.superseded.outputs.run_agent == 'true' + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + + - name: Ensure docs agent key exists + if: steps.superseded.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.superseded.outputs.run_agent == 'true' + uses: openai/codex-action@v1 + with: + openai-api-key: ${{ secrets.OPENCLAW_DOCS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }} + prompt-file: .github/codex/prompts/docs-agent.md + model: gpt-5.4 + effort: medium + sandbox: workspace-write + safety-strategy: drop-sudo + codex-args: '["--full-auto"]' + + - name: Enforce existing-docs-only patch + if: steps.superseded.outputs.run_agent == 'true' + run: | + set -euo pipefail + + untracked="$(git 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="$(git diff --name-status --diff-filter=AD)" + if [ -n "$added_or_deleted" ]; then + echo "Docs agent added or deleted tracked files; forbidden:" + printf '%s\n' "$added_or_deleted" + exit 1 + fi + + bad_paths="$( + git diff --name-only | while IFS= read -r path; do + case "$path" in + docs/*|README.md|CHANGELOG.md) ;; + *) printf '%s\n' "$path" ;; + esac + done + )" + if [ -n "$bad_paths" ]; then + echo "Docs agent touched non-doc paths; forbidden:" + printf '%s\n' "$bad_paths" + exit 1 + fi + + - name: Restore Node environment + if: steps.superseded.outputs.run_agent == 'true' + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + install-deps: "false" + + - name: Check docs + if: steps.superseded.outputs.run_agent == 'true' + run: pnpm check:docs + + - name: Commit docs updates + if: steps.superseded.outputs.run_agent == 'true' + env: + BASE_SHA: ${{ steps.superseded.outputs.base_sha }} + GITHUB_TOKEN: ${{ github.token }} + TARGET_BRANCH: main + run: | + set -euo pipefail + + if git diff --quiet; then + echo "No docs changes." + exit 0 + fi + + git fetch --no-tags origin "${TARGET_BRANCH}" + remote_main="$(git rev-parse "origin/${TARGET_BRANCH}")" + if [ "$remote_main" != "$BASE_SHA" ]; then + echo "main advanced from ${BASE_SHA} to ${remote_main}; skipping stale docs update." + exit 0 + fi + + git config user.name "openclaw-docs-agent[bot]" + git config user.email "openclaw-docs-agent[bot]@users.noreply.github.com" + git add docs README.md CHANGELOG.md + git commit --no-verify -m "docs: refresh documentation" + + for attempt in 1 2 3 4 5; do + if git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:"${TARGET_BRANCH}"; then + exit 0 + fi + git fetch --no-tags origin "${TARGET_BRANCH}" + remote_main="$(git rev-parse "origin/${TARGET_BRANCH}")" + if [ "$remote_main" != "$BASE_SHA" ]; then + echo "main advanced from ${BASE_SHA} to ${remote_main}; skipping stale docs update." + exit 0 + fi + echo "Push attempt ${attempt} failed; retrying." + sleep $((attempt * 2)) + done + + echo "Failed to push docs updates after retries." >&2 + exit 1