| 1 | name: Trigger podcast generation |
| 2 | |
| 3 | on: |
| 4 | workflow_dispatch: |
| 5 | # checkov:skip=CKV_GHA_7:Operational dispatch workflow (not a release build). Inputs select the target week/run-id and optional breaking-news context; they do not alter published build artifacts. Inputs are required for manual operation. |
| 6 | inputs: |
| 7 | week: |
| 8 | description: 'Week slug, e.g. 2026-W25' |
| 9 | required: true |
| 10 | type: string |
| 11 | publish_run_id: |
| 12 | description: 'Optional: crawl-and-publish workflow run ID that produced the manifest. If omitted, uses the most recent.' |
| 13 | required: false |
| 14 | type: string |
| 15 | breaking_news: |
| 16 | description: 'Optional last-moment news or important information to include in this podcast episode.' |
| 17 | required: false |
| 18 | type: string |
| 19 | |
| 20 | permissions: |
| 21 | contents: read |
| 22 | |
| 23 | concurrency: |
| 24 | group: ${{ github.workflow }}-${{ github.ref }} |
| 25 | cancel-in-progress: false |
| 26 | |
| 27 | jobs: |
| 28 | trigger-podcast: |
| 29 | runs-on: ubuntu-latest |
| 30 | permissions: |
| 31 | contents: read |
| 32 | |
| 33 | steps: |
| 34 | - name: Check out repository |
| 35 | uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 |
| 36 | with: |
| 37 | persist-credentials: false |
| 38 | |
| 39 | - name: Set up Python |
| 40 | uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 |
| 41 | with: |
| 42 | python-version: '3.12' |
| 43 | |
| 44 | - name: Derive paths from week slug |
| 45 | id: derive |
| 46 | env: |
| 47 | WEEK: ${{ inputs.week }} |
| 48 | run: | |
| 49 | set -euo pipefail |
| 50 | if [[ ! "$WEEK" =~ ^[0-9]{4}-W[0-9]{2}$ ]]; then |
| 51 | echo "::error::week must match YYYY-WNN (two-digit week), e.g. 2026-W25 (got: $WEEK)" |
| 52 | exit 1 |
| 53 | fi |
| 54 | |
| 55 | # 2026-W25 → year=2026, short=W25 |
| 56 | YEAR="${WEEK%%-*}" |
| 57 | SHORT="${WEEK##*-}" |
| 58 | echo "article_path=content/weekly/${YEAR}/${SHORT}.md" >> "$GITHUB_OUTPUT" |
| 59 | echo "article_url=https://claracle.com/weekly/${YEAR}/${SHORT}/" >> "$GITHUB_OUTPUT" |
| 60 | |
| 61 | - name: Locate publish manifest from crawl-and-publish |
| 62 | id: manifest-locate |
| 63 | env: |
| 64 | WEEK: ${{ inputs.week }} |
| 65 | PUBLISH_RUN_ID_INPUT: ${{ inputs.publish_run_id }} |
| 66 | run: | |
| 67 | set -euo pipefail |
| 68 | if ! git fetch origin publish 2>/dev/null; then |
| 69 | echo "::error::Could not fetch publish branch. Has crawl-and-publish run yet?" |
| 70 | exit 1 |
| 71 | fi |
| 72 | git checkout origin/publish -- data/candidates/ 2>/dev/null || true |
| 73 | |
| 74 | if [ -n "$PUBLISH_RUN_ID_INPUT" ]; then |
| 75 | # Use specified run ID |
| 76 | if ! [[ "$PUBLISH_RUN_ID_INPUT" =~ ^[0-9]+$ ]]; then |
| 77 | echo "::error::publish_run_id must be a numeric workflow run ID (got: $PUBLISH_RUN_ID_INPUT)" |
| 78 | exit 1 |
| 79 | fi |
| 80 | MANIFEST="data/candidates/${WEEK}/${PUBLISH_RUN_ID_INPUT}/publish-manifest.json" |
| 81 | if [ ! -f "$MANIFEST" ]; then |
| 82 | echo "::error::Manifest not found at $MANIFEST for specified run $PUBLISH_RUN_ID_INPUT" |
| 83 | exit 1 |
| 84 | fi |
| 85 | echo "manifest_path=$MANIFEST" >> "$GITHUB_OUTPUT" |
| 86 | echo "publish_run_id=$PUBLISH_RUN_ID_INPUT" >> "$GITHUB_OUTPUT" |
| 87 | echo "::notice::Using manifest from specified run: $PUBLISH_RUN_ID_INPUT" |
| 88 | else |
| 89 | # Find the most recent manifest for this week |
| 90 | MANIFEST=$(find "data/candidates/${WEEK}" -name 'publish-manifest.json' -type f 2>/dev/null | sort -V | tail -1) |
| 91 | if [ -z "$MANIFEST" ]; then |
| 92 | echo "::error::No manifest found for week $WEEK in data/candidates/. Has crawl-and-publish completed for this week?" |
| 93 | exit 1 |
| 94 | fi |
| 95 | # Extract run ID from path: data/candidates/WEEK/RUN_ID/publish-manifest.json |
| 96 | RUN_ID="$(basename "$(dirname "$MANIFEST")")" |
| 97 | echo "manifest_path=$MANIFEST" >> "$GITHUB_OUTPUT" |
| 98 | echo "publish_run_id=$RUN_ID" >> "$GITHUB_OUTPUT" |
| 99 | echo "::notice::Using manifest from most recent run: $RUN_ID" |
| 100 | fi |
| 101 | |
| 102 | - name: Trigger podcast generation with existing manifest |
| 103 | env: |
| 104 | PODCASTER_ENDPOINT: ${{ vars.PODCASTER_ENDPOINT }} |
| 105 | PODCASTER_API_KEY: ${{ secrets.PODCASTER_API_KEY }} |
| 106 | WEEK: ${{ inputs.week }} |
| 107 | ARTICLE_URL: ${{ steps.derive.outputs.article_url }} |
| 108 | ARTICLE_PATH: ${{ steps.derive.outputs.article_path }} |
| 109 | MANIFEST_FILE: ${{ steps.manifest-locate.outputs.manifest_path }} |
| 110 | PUBLISH_RUN_ID: ${{ steps.manifest-locate.outputs.publish_run_id }} |
| 111 | TRIGGER_RUN_ID: ${{ github.run_id }} |
| 112 | BREAKING_NEWS: ${{ inputs.breaking_news || '' }} |
| 113 | run: | |
| 114 | set -euo pipefail |
| 115 | if [ -z "$PODCASTER_ENDPOINT" ] || [ -z "$PODCASTER_API_KEY" ]; then |
| 116 | echo "::error::Requires PODCASTER_ENDPOINT variable and PODCASTER_API_KEY secret." |
| 117 | exit 1 |
| 118 | fi |
| 119 | if [ ! -f "$ARTICLE_PATH" ]; then |
| 120 | # Article may already be published on the publish branch but not yet |
| 121 | # merged into main via the sync PR. Fall back to the publish branch. |
| 122 | git checkout origin/publish -- "$ARTICLE_PATH" 2>/dev/null || { |
| 123 | echo "::error::Article '$ARTICLE_PATH' does not exist on main or the publish branch." |
| 124 | exit 1 |
| 125 | } |
| 126 | fi |
| 127 | if [ ! -f "$MANIFEST_FILE" ]; then |
| 128 | echo "::error::Manifest file not found: $MANIFEST_FILE" |
| 129 | exit 1 |
| 130 | fi |
| 131 | echo "::notice::Using manifest from crawl-and-publish run $PUBLISH_RUN_ID ($(date -r "$MANIFEST_FILE" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo 'date unknown'))" |
| 132 | |
| 133 | BREAKING_ARGS=() |
| 134 | if [ -n "$BREAKING_NEWS" ]; then |
| 135 | BREAKING_ARGS=(--breaking-news "$BREAKING_NEWS") |
| 136 | fi |
| 137 | |
| 138 | # Trigger real podcast generation using the existing validated manifest |
| 139 | python3 scripts/podcaster_handoff.py \ |
| 140 | --week "$WEEK" \ |
| 141 | --article-url "$ARTICLE_URL" \ |
| 142 | --article-path "$ARTICLE_PATH" \ |
| 143 | --publish-run-id "$PUBLISH_RUN_ID" \ |
| 144 | --publish-mode normal \ |
| 145 | --manifest "$MANIFEST_FILE" \ |
| 146 | --podcast-config config/podcast.json \ |
| 147 | "${BREAKING_ARGS[@]}" |