Simplify trigger-podcast workflow to single 'week' input (#499)

* feat: add trigger-podcast workflow for manual podcast generation Allows re-triggering podcast generation for an already-published week without running the full crawl pipeline. Takes week, article_url, and article_path as inputs and calls the handoff script in real mode. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: simplify trigger-podcast to single 'week' input Derive article_path and article_url from the week slug automatically. No need for redundant inputs — 2026-W25 → content/weekly/2026/W25.md and https://claracle.com/weekly/2026/W25/. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat: enhance trigger-podcast workflow to support optional publish_run_id and improve manifest handling * refactor: streamline week input handling in trigger-podcast workflow * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: jmservera <jmservera@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Juan Manuel Servera committed Jun 15, 2026 at 22:36 UTC 59e7b17ef18315762b96e15f4234e27dfdc0cc83
1 file changed +76 -64
.github/workflows/trigger-podcast.yml
+76 -64
@@ -4,16 +4,12 @@ on:
4 workflow_dispatch:
5 inputs:
6 week:
7 - description: 'Week slug, e.g. 2026-W25.'
7 + description: 'Week slug, e.g. 2026-W25'
8 required: true
9 type: string
10 - article_url:
11 - description: 'Published SquadScope article URL.'
12 - required: true
13 - type: string
14 - article_path:
15 - description: 'Article content path, e.g. content/weekly/2026/W25.md.'
16 - required: true
10 + publish_run_id:
11 + description: 'Optional: crawl-and-publish workflow run ID that produced the manifest. If omitted, uses the most recent.'
12 + required: false
13 type: string
14
15 permissions:
@@ -36,14 +32,74 @@ jobs:
32 with:
33 python-version: '3.12'
34
39 - - name: Build manifest and trigger podcast generation
35 + - name: Derive paths from week slug
36 + id: derive
37 + env:
38 + WEEK: ${{ inputs.week }}
39 + run: |
40 + set -euo pipefail
41 + if [[ ! "$WEEK" =~ ^[0-9]{4}-W[0-9]{2}$ ]]; then
42 + echo "::error::week must match YYYY-WNN (two-digit week), e.g. 2026-W25 (got: $WEEK)"
43 + exit 1
44 + fi
45 +
46 + # 2026-W25 → year=2026, short=W25
47 + YEAR="${WEEK%%-*}"
48 + SHORT="${WEEK##*-}"
49 + echo "article_path=content/weekly/${YEAR}/${SHORT}.md" >> "$GITHUB_OUTPUT"
50 + echo "article_url=https://claracle.com/weekly/${YEAR}/${SHORT}/" >> "$GITHUB_OUTPUT"
51 +
52 + - name: Locate publish manifest from crawl-and-publish
53 + id: manifest-locate
54 + env:
55 + WEEK: ${{ inputs.week }}
56 + PUBLISH_RUN_ID_INPUT: ${{ inputs.publish_run_id }}
57 + run: |
58 + set -euo pipefail
59 + if ! git fetch origin publish 2>/dev/null; then
60 + echo "::error::Could not fetch publish branch. Has crawl-and-publish run yet?"
61 + exit 1
62 + fi
63 + git checkout origin/publish -- data/candidates/ 2>/dev/null || true
64 +
65 + if [ -n "$PUBLISH_RUN_ID_INPUT" ]; then
66 + # Use specified run ID
67 + if ! [[ "$PUBLISH_RUN_ID_INPUT" =~ ^[0-9]+$ ]]; then
68 + echo "::error::publish_run_id must be a numeric workflow run ID (got: $PUBLISH_RUN_ID_INPUT)"
69 + exit 1
70 + fi
71 + MANIFEST="data/candidates/${WEEK}/${PUBLISH_RUN_ID_INPUT}/publish-manifest.json"
72 + if [ ! -f "$MANIFEST" ]; then
73 + echo "::error::Manifest not found at $MANIFEST for specified run $PUBLISH_RUN_ID_INPUT"
74 + exit 1
75 + fi
76 + echo "manifest_path=$MANIFEST" >> "$GITHUB_OUTPUT"
77 + echo "publish_run_id=$PUBLISH_RUN_ID_INPUT" >> "$GITHUB_OUTPUT"
78 + echo "::notice::Using manifest from specified run: $PUBLISH_RUN_ID_INPUT"
79 + else
80 + # Find the most recent manifest for this week
81 + MANIFEST=$(find "data/candidates/${WEEK}" -name 'publish-manifest.json' -type f 2>/dev/null | sort -V | tail -1)
82 + if [ -z "$MANIFEST" ]; then
83 + echo "::error::No manifest found for week $WEEK in data/candidates/. Has crawl-and-publish completed for this week?"
84 + exit 1
85 + fi
86 + # Extract run ID from path: data/candidates/WEEK/RUN_ID/publish-manifest.json
87 + RUN_ID="$(basename "$(dirname "$MANIFEST")")"
88 + echo "manifest_path=$MANIFEST" >> "$GITHUB_OUTPUT"
89 + echo "publish_run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
90 + echo "::notice::Using manifest from most recent run: $RUN_ID"
91 + fi
92 +
93 + - name: Trigger podcast generation with existing manifest
94 env:
95 PODCASTER_ENDPOINT: ${{ vars.PODCASTER_ENDPOINT }}
96 PODCASTER_API_KEY: ${{ secrets.PODCASTER_API_KEY }}
97 WEEK: ${{ inputs.week }}
44 - ARTICLE_URL: ${{ inputs.article_url }}
45 - ARTICLE_PATH: ${{ inputs.article_path }}
46 - PUBLISH_RUN_ID: ${{ github.run_id }}
98 + ARTICLE_URL: ${{ steps.derive.outputs.article_url }}
99 + ARTICLE_PATH: ${{ steps.derive.outputs.article_path }}
100 + MANIFEST_FILE: ${{ steps.manifest-locate.outputs.manifest_path }}
101 + PUBLISH_RUN_ID: ${{ steps.manifest-locate.outputs.publish_run_id }}
102 + TRIGGER_RUN_ID: ${{ github.run_id }}
103 run: |
104 set -euo pipefail
105 if [ -z "$PODCASTER_ENDPOINT" ] || [ -z "$PODCASTER_API_KEY" ]; then
@@ -51,65 +107,21 @@ jobs:
107 exit 1
108 fi
109 if [ ! -f "$ARTICLE_PATH" ]; then
54 - echo "::error::article_path '$ARTICLE_PATH' does not exist in the repository."
110 + echo "::error::Article '$ARTICLE_PATH' does not exist in the repository."
111 exit 1
112 fi
113 + if [ ! -f "$MANIFEST_FILE" ]; then
114 + echo "::error::Manifest file not found: $MANIFEST_FILE"
115 + exit 1
116 + fi
117 + 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'))"
118
58 - # Build a minimal publish manifest for the handoff script
59 - mkdir -p .podcast-trigger
60 - python3 - <<'PY' "$WEEK" "$ARTICLE_PATH" .podcast-trigger/publish-manifest.json
61 - import hashlib
62 - import json
63 - import sys
64 - from pathlib import Path
65 -
66 - week, article_path, manifest_path = sys.argv[1:]
67 - article = Path(article_path)
68 - article_bytes = article.read_bytes()
69 - article_sha = hashlib.sha256(article_bytes).hexdigest()
70 - raw_payload = {"week": week, "source": "github", "article_path": article_path}
71 - raw_bytes = (json.dumps(raw_payload, sort_keys=True) + "\n").encode("utf-8")
72 - raw_sha = hashlib.sha256(raw_bytes).hexdigest()
73 -
74 - manifest = {
75 - "week": week,
76 - "run_mode": "normal",
77 - "candidate": {"summary_sha256": article_sha},
78 - "analysis": {"ai_status": "ai"},
79 - "promotion": {"eligible": True, "decision": "promote"},
80 - "source_artifacts": [
81 - {
82 - "role": "raw_github",
83 - "path": f"data/raw/{week}.json",
84 - "name": f"{week}-raw-github",
85 - "sha256": raw_sha,
86 - "generated_at": "2026-01-01T00:00:00Z",
87 - "source_status": "fresh",
88 - "exists": True,
89 - "size_bytes": len(raw_bytes),
90 - "freshness": {"status": "fresh", "reasons": []},
91 - "provenance": {"path": f"data/raw/{week}.json", "sha256": raw_sha},
92 - },
93 - {
94 - "role": "published_summary",
95 - "path": article_path,
96 - "sha256": article_sha,
97 - "generated_at": "2026-01-01T00:00:00Z",
98 - "exists": True,
99 - "size_bytes": len(article_bytes),
100 - "provenance": {"path": article_path, "sha256": article_sha},
101 - },
102 - ],
103 - }
104 - Path(manifest_path).write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
105 - PY
106 -
107 - # Trigger real podcast generation (no --podcaster-dry-run)
119 + # Trigger real podcast generation using the existing validated manifest
120 python3 scripts/podcaster_handoff.py \
121 --week "$WEEK" \
122 --article-url "$ARTICLE_URL" \
123 --article-path "$ARTICLE_PATH" \
124 --publish-run-id "$PUBLISH_RUN_ID" \
125 --publish-mode normal \
114 - --manifest .podcast-trigger/publish-manifest.json \
126 + --manifest "$MANIFEST_FILE" \
127 --podcast-config config/podcast.json