mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
This pull request updates the `.github/workflows/doc-check.yaml` workflow to improve its handling of required secrets. The workflow now checks for the presence of necessary secrets before proceeding, and conditionally skips all subsequent steps if the secrets are unavailable. This prevents failures on pull requests where secrets are not accessible (such as from forks), and provides clear messaging for maintainers about manual triggering options. Key improvements: **Secret availability checks and conditional execution:** * Added an explicit step at the start of the workflow to check if required secrets (`DOC_CHECK_CODER_URL` and `DOC_CHECK_CODER_SESSION_TOKEN`) are available, and set an output flag (`skip`) accordingly. * Updated all subsequent workflow steps to include a conditional (`if: steps.check-secrets.outputs.skip != 'true'`), ensuring they only run if the secrets are present. This includes setup, context extraction, task creation, waiting, and summary steps. [[1]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149R59-R82) [[2]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149R140) [[3]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149R205) [[4]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149R215) [[5]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149R232) [[6]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149R250) * Modified the "Fetch Task Logs", "Cleanup Task", and "Write Final Summary" steps to combine their existing `always()` condition with the new secrets check, preventing unnecessary errors when secrets are missing. [[1]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149L314-R340) [[2]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149L327-R353) [[3]](diffhunk://#diff-46e6065a312f35e5d294476e7865089afd10e6072fed80ac77b257e090def149L339-R365) **Documentation and messaging:** * Added comments at the top of the workflow file to explain the secret requirements and the expected behavior for PRs without secrets, including instructions for maintainers on manual triggering.…se on pr's originating from forks. <!-- If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting. -->
390 lines
15 KiB
YAML
390 lines
15 KiB
YAML
# This workflow checks if a PR requires documentation updates.
|
|
# It creates a Coder Task that uses AI to analyze the PR changes,
|
|
# search existing docs, and comment with recommendations.
|
|
#
|
|
# Triggers:
|
|
# - New PR opened: Initial documentation review
|
|
# - PR updated (synchronize): Re-review after changes
|
|
# - Label "doc-check" added: Manual trigger for review
|
|
# - Workflow dispatch: Manual run with PR URL
|
|
#
|
|
# Note: This workflow requires access to secrets and will be skipped for:
|
|
# - Any PR where secrets are not available
|
|
# For these PRs, maintainers can manually trigger via workflow_dispatch.
|
|
|
|
name: AI Documentation Check
|
|
|
|
on:
|
|
pull_request:
|
|
types:
|
|
- opened
|
|
- synchronize
|
|
- labeled
|
|
workflow_dispatch:
|
|
inputs:
|
|
pr_url:
|
|
description: "Pull Request URL to check"
|
|
required: true
|
|
type: string
|
|
template_preset:
|
|
description: "Template preset to use"
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
jobs:
|
|
doc-check:
|
|
name: Analyze PR for Documentation Updates Needed
|
|
runs-on: ubuntu-latest
|
|
# Run on: opened, synchronize, labeled (with doc-check label), or workflow_dispatch
|
|
# Skip draft PRs unless manually triggered
|
|
if: |
|
|
(
|
|
github.event.action == 'opened' ||
|
|
github.event.action == 'synchronize' ||
|
|
github.event.label.name == 'doc-check' ||
|
|
github.event_name == 'workflow_dispatch'
|
|
) &&
|
|
(github.event.pull_request.draft == false || github.event_name == 'workflow_dispatch')
|
|
timeout-minutes: 30
|
|
env:
|
|
CODER_URL: ${{ secrets.DOC_CHECK_CODER_URL }}
|
|
CODER_SESSION_TOKEN: ${{ secrets.DOC_CHECK_CODER_SESSION_TOKEN }}
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
actions: write
|
|
|
|
steps:
|
|
- name: Check if secrets are available
|
|
id: check-secrets
|
|
env:
|
|
CODER_URL: ${{ secrets.DOC_CHECK_CODER_URL }}
|
|
CODER_TOKEN: ${{ secrets.DOC_CHECK_CODER_SESSION_TOKEN }}
|
|
run: |
|
|
if [[ -z "${CODER_URL}" || -z "${CODER_TOKEN}" ]]; then
|
|
echo "skip=true" >> "${GITHUB_OUTPUT}"
|
|
echo "Secrets not available - skipping doc-check."
|
|
echo "This is expected for PRs where secrets are not available."
|
|
echo "Maintainers can manually trigger via workflow_dispatch if needed."
|
|
{
|
|
echo "⚠️ Workflow skipped: Secrets not available"
|
|
echo ""
|
|
echo "This workflow requires secrets that are unavailable for this run."
|
|
echo "Maintainers can manually trigger via workflow_dispatch if needed."
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|
|
else
|
|
echo "skip=false" >> "${GITHUB_OUTPUT}"
|
|
fi
|
|
|
|
- name: Setup Coder CLI
|
|
if: steps.check-secrets.outputs.skip != 'true'
|
|
uses: coder/setup-action@4a607a8113d4e676e2d7c34caa20a814bc88bfda # v1
|
|
with:
|
|
access_url: ${{ secrets.DOC_CHECK_CODER_URL }}
|
|
coder_session_token: ${{ secrets.DOC_CHECK_CODER_SESSION_TOKEN }}
|
|
|
|
- name: Determine PR Context
|
|
if: steps.check-secrets.outputs.skip != 'true'
|
|
id: determine-context
|
|
env:
|
|
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
|
GITHUB_EVENT_ACTION: ${{ github.event.action }}
|
|
GITHUB_EVENT_PR_HTML_URL: ${{ github.event.pull_request.html_url }}
|
|
GITHUB_EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
INPUTS_PR_URL: ${{ inputs.pr_url }}
|
|
INPUTS_TEMPLATE_PRESET: ${{ inputs.template_preset || '' }}
|
|
run: |
|
|
echo "Using template preset: ${INPUTS_TEMPLATE_PRESET}"
|
|
echo "template_preset=${INPUTS_TEMPLATE_PRESET}" >> "${GITHUB_OUTPUT}"
|
|
|
|
# Determine trigger type for task context
|
|
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
|
echo "trigger_type=manual" >> "${GITHUB_OUTPUT}"
|
|
echo "Using PR URL: ${INPUTS_PR_URL}"
|
|
|
|
# Validate PR URL format
|
|
if [[ ! "${INPUTS_PR_URL}" =~ ^https://github\.com/[^/]+/[^/]+/pull/[0-9]+$ ]]; then
|
|
echo "::error::Invalid PR URL format: ${INPUTS_PR_URL}"
|
|
echo "::error::Expected format: https://github.com/owner/repo/pull/NUMBER"
|
|
exit 1
|
|
fi
|
|
|
|
ISSUE_URL="${INPUTS_PR_URL/\/pull\//\/issues\/}"
|
|
echo "pr_url=${ISSUE_URL}" >> "${GITHUB_OUTPUT}"
|
|
PR_NUMBER=$(echo "${INPUTS_PR_URL}" | grep -oP '(?<=pull/)\d+')
|
|
echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
|
|
|
elif [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
|
|
echo "Using PR URL: ${GITHUB_EVENT_PR_HTML_URL}"
|
|
ISSUE_URL="${GITHUB_EVENT_PR_HTML_URL/\/pull\//\/issues\/}"
|
|
echo "pr_url=${ISSUE_URL}" >> "${GITHUB_OUTPUT}"
|
|
echo "pr_number=${GITHUB_EVENT_PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
|
|
|
# Set trigger type based on action
|
|
case "${GITHUB_EVENT_ACTION}" in
|
|
opened)
|
|
echo "trigger_type=new_pr" >> "${GITHUB_OUTPUT}"
|
|
;;
|
|
synchronize)
|
|
echo "trigger_type=pr_updated" >> "${GITHUB_OUTPUT}"
|
|
;;
|
|
labeled)
|
|
echo "trigger_type=label_requested" >> "${GITHUB_OUTPUT}"
|
|
;;
|
|
*)
|
|
echo "trigger_type=unknown" >> "${GITHUB_OUTPUT}"
|
|
;;
|
|
esac
|
|
|
|
else
|
|
echo "::error::Unsupported event type: ${GITHUB_EVENT_NAME}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Build task prompt
|
|
if: steps.check-secrets.outputs.skip != 'true'
|
|
id: extract-context
|
|
env:
|
|
PR_NUMBER: ${{ steps.determine-context.outputs.pr_number }}
|
|
TRIGGER_TYPE: ${{ steps.determine-context.outputs.trigger_type }}
|
|
run: |
|
|
echo "Analyzing PR #${PR_NUMBER} (trigger: ${TRIGGER_TYPE})"
|
|
|
|
# Build context based on trigger type
|
|
case "${TRIGGER_TYPE}" in
|
|
new_pr)
|
|
CONTEXT="This is a NEW PR. Perform a thorough documentation review."
|
|
;;
|
|
pr_updated)
|
|
CONTEXT="This PR was UPDATED with new commits. Only comment if the changes affect documentation needs or address previous feedback."
|
|
;;
|
|
label_requested)
|
|
CONTEXT="A documentation review was REQUESTED via label. Perform a thorough documentation review."
|
|
;;
|
|
manual)
|
|
CONTEXT="This is a MANUAL review request. Perform a thorough documentation review."
|
|
;;
|
|
*)
|
|
CONTEXT="Perform a thorough documentation review."
|
|
;;
|
|
esac
|
|
|
|
# Build task prompt with PR-specific context
|
|
TASK_PROMPT="Use the doc-check skill to review PR #${PR_NUMBER} in coder/coder.
|
|
|
|
${CONTEXT}
|
|
|
|
Use \`gh\` to get PR details, diff, and all comments. Check for previous doc-check comments (from coder-doc-check) and only post a new comment if it adds value.
|
|
|
|
## Comment format
|
|
|
|
Use this structure (only include relevant sections):
|
|
|
|
\`\`\`
|
|
## Documentation Check
|
|
|
|
### Previous Feedback
|
|
[For re-reviews only: Addressed | Partially addressed | Not yet addressed]
|
|
|
|
### Updates Needed
|
|
- [ ] \`docs/path/file.md\` - [what needs to change]
|
|
|
|
### New Documentation Needed
|
|
- [ ] \`docs/suggested/path.md\` - [what should be documented]
|
|
|
|
### No Changes Needed
|
|
[brief explanation - use this OR the above sections, not both]
|
|
|
|
---
|
|
*Automated review via [Coder Tasks](https://coder.com/docs/ai-coder/tasks)*
|
|
\`\`\`"
|
|
|
|
# Output the prompt
|
|
{
|
|
echo "task_prompt<<EOFOUTPUT"
|
|
echo "${TASK_PROMPT}"
|
|
echo "EOFOUTPUT"
|
|
} >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: Checkout create-task-action
|
|
if: steps.check-secrets.outputs.skip != 'true'
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
with:
|
|
fetch-depth: 1
|
|
path: ./.github/actions/create-task-action
|
|
persist-credentials: false
|
|
ref: main
|
|
repository: coder/create-task-action
|
|
|
|
- name: Create Coder Task for Documentation Check
|
|
if: steps.check-secrets.outputs.skip != 'true'
|
|
id: create_task
|
|
uses: ./.github/actions/create-task-action
|
|
with:
|
|
coder-url: ${{ secrets.DOC_CHECK_CODER_URL }}
|
|
coder-token: ${{ secrets.DOC_CHECK_CODER_SESSION_TOKEN }}
|
|
coder-organization: "default"
|
|
coder-template-name: coder-workflow-bot
|
|
coder-template-preset: ${{ steps.determine-context.outputs.template_preset }}
|
|
coder-task-name-prefix: doc-check
|
|
coder-task-prompt: ${{ steps.extract-context.outputs.task_prompt }}
|
|
coder-username: doc-check-bot
|
|
github-token: ${{ github.token }}
|
|
github-issue-url: ${{ steps.determine-context.outputs.pr_url }}
|
|
comment-on-issue: true
|
|
|
|
- name: Write Task Info
|
|
if: steps.check-secrets.outputs.skip != 'true'
|
|
env:
|
|
TASK_CREATED: ${{ steps.create_task.outputs.task-created }}
|
|
TASK_NAME: ${{ steps.create_task.outputs.task-name }}
|
|
TASK_URL: ${{ steps.create_task.outputs.task-url }}
|
|
PR_URL: ${{ steps.determine-context.outputs.pr_url }}
|
|
run: |
|
|
{
|
|
echo "## Documentation Check Task"
|
|
echo ""
|
|
echo "**PR:** ${PR_URL}"
|
|
echo "**Task created:** ${TASK_CREATED}"
|
|
echo "**Task name:** ${TASK_NAME}"
|
|
echo "**Task URL:** ${TASK_URL}"
|
|
echo ""
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|
|
|
|
- name: Wait for Task Completion
|
|
if: steps.check-secrets.outputs.skip != 'true'
|
|
id: wait_task
|
|
env:
|
|
TASK_NAME: ${{ steps.create_task.outputs.task-name }}
|
|
run: |
|
|
echo "Waiting for task to complete..."
|
|
echo "Task name: ${TASK_NAME}"
|
|
|
|
if [[ -z "${TASK_NAME}" ]]; then
|
|
echo "::error::TASK_NAME is empty"
|
|
exit 1
|
|
fi
|
|
|
|
MAX_WAIT=600 # 10 minutes
|
|
WAITED=0
|
|
POLL_INTERVAL=3
|
|
LAST_STATUS=""
|
|
|
|
is_workspace_message() {
|
|
local msg="$1"
|
|
[[ -z "$msg" ]] && return 0 # Empty = treat as workspace/startup
|
|
[[ "$msg" =~ ^Workspace ]] && return 0
|
|
[[ "$msg" =~ ^Agent ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
while [[ $WAITED -lt $MAX_WAIT ]]; do
|
|
# Get task status (|| true prevents set -e from exiting on non-zero)
|
|
RAW_OUTPUT=$(coder task status "${TASK_NAME}" -o json 2>&1) || true
|
|
STATUS_JSON=$(echo "$RAW_OUTPUT" | grep -v "^version mismatch\|^download v" || true)
|
|
|
|
# Debug: show first poll's raw output
|
|
if [[ $WAITED -eq 0 ]]; then
|
|
echo "Raw status output: ${RAW_OUTPUT:0:500}"
|
|
fi
|
|
|
|
if [[ -z "$STATUS_JSON" ]] || ! echo "$STATUS_JSON" | jq -e . >/dev/null 2>&1; then
|
|
if [[ "$LAST_STATUS" != "waiting" ]]; then
|
|
echo "[${WAITED}s] Waiting for task status..."
|
|
LAST_STATUS="waiting"
|
|
fi
|
|
sleep $POLL_INTERVAL
|
|
WAITED=$((WAITED + POLL_INTERVAL))
|
|
continue
|
|
fi
|
|
|
|
TASK_STATE=$(echo "$STATUS_JSON" | jq -r '.current_state.state // "unknown"')
|
|
TASK_MESSAGE=$(echo "$STATUS_JSON" | jq -r '.current_state.message // ""')
|
|
WORKSPACE_STATUS=$(echo "$STATUS_JSON" | jq -r '.workspace_status // "unknown"')
|
|
|
|
# Build current status string for comparison
|
|
CURRENT_STATUS="${TASK_STATE}|${WORKSPACE_STATUS}|${TASK_MESSAGE}"
|
|
|
|
# Only log if status changed
|
|
if [[ "$CURRENT_STATUS" != "$LAST_STATUS" ]]; then
|
|
if [[ "$TASK_STATE" == "idle" ]] && is_workspace_message "$TASK_MESSAGE"; then
|
|
echo "[${WAITED}s] Workspace ready, waiting for Agent..."
|
|
else
|
|
echo "[${WAITED}s] State: ${TASK_STATE} | Workspace: ${WORKSPACE_STATUS} | ${TASK_MESSAGE}"
|
|
fi
|
|
LAST_STATUS="$CURRENT_STATUS"
|
|
fi
|
|
|
|
if [[ "$WORKSPACE_STATUS" == "failed" || "$WORKSPACE_STATUS" == "canceled" ]]; then
|
|
echo "::error::Workspace failed: ${WORKSPACE_STATUS}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$TASK_STATE" == "idle" ]]; then
|
|
if ! is_workspace_message "$TASK_MESSAGE"; then
|
|
# Real completion message from Claude!
|
|
echo ""
|
|
echo "Task completed: ${TASK_MESSAGE}"
|
|
RESULT_URI=$(echo "$STATUS_JSON" | jq -r '.current_state.uri // ""')
|
|
echo "result_uri=${RESULT_URI}" >> "${GITHUB_OUTPUT}"
|
|
echo "task_message=${TASK_MESSAGE}" >> "${GITHUB_OUTPUT}"
|
|
break
|
|
fi
|
|
fi
|
|
|
|
sleep $POLL_INTERVAL
|
|
WAITED=$((WAITED + POLL_INTERVAL))
|
|
done
|
|
|
|
if [[ $WAITED -ge $MAX_WAIT ]]; then
|
|
echo "::error::Task monitoring timed out after ${MAX_WAIT}s"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Fetch Task Logs
|
|
if: always() && steps.check-secrets.outputs.skip != 'true'
|
|
env:
|
|
TASK_NAME: ${{ steps.create_task.outputs.task-name }}
|
|
run: |
|
|
echo "::group::Task Conversation Log"
|
|
if [[ -n "${TASK_NAME}" ]]; then
|
|
coder task logs "${TASK_NAME}" 2>&1 || echo "Failed to fetch logs"
|
|
else
|
|
echo "No task name, skipping log fetch"
|
|
fi
|
|
echo "::endgroup::"
|
|
|
|
- name: Cleanup Task
|
|
if: always() && steps.check-secrets.outputs.skip != 'true'
|
|
env:
|
|
TASK_NAME: ${{ steps.create_task.outputs.task-name }}
|
|
run: |
|
|
if [[ -n "${TASK_NAME}" ]]; then
|
|
echo "Deleting task: ${TASK_NAME}"
|
|
coder task delete "${TASK_NAME}" -y 2>&1 || echo "Task deletion failed or already deleted"
|
|
else
|
|
echo "No task name, skipping cleanup"
|
|
fi
|
|
|
|
- name: Write Final Summary
|
|
if: always() && steps.check-secrets.outputs.skip != 'true'
|
|
env:
|
|
TASK_NAME: ${{ steps.create_task.outputs.task-name }}
|
|
TASK_MESSAGE: ${{ steps.wait_task.outputs.task_message }}
|
|
RESULT_URI: ${{ steps.wait_task.outputs.result_uri }}
|
|
PR_NUMBER: ${{ steps.determine-context.outputs.pr_number }}
|
|
run: |
|
|
{
|
|
echo ""
|
|
echo "---"
|
|
echo "### Result"
|
|
echo ""
|
|
echo "**Status:** ${TASK_MESSAGE:-Task completed}"
|
|
if [[ -n "${RESULT_URI}" ]]; then
|
|
echo "**Comment:** ${RESULT_URI}"
|
|
fi
|
|
echo ""
|
|
echo "Task \`${TASK_NAME}\` has been cleaned up."
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|