Repository navigation
Weekly X Posts #49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Weekly X Posts | |
| on: | |
| schedule: | |
| # 00:15 UTC every Monday — the week's evergreen X batch (6/day × 7). | |
| - cron: "15 0 * * 1" | |
| workflow_dispatch: | |
| inputs: | |
| date: | |
| description: "Week's Monday YYYY-MM-DD (omit for this week, UTC)" | |
| required: false | |
| type: string | |
| dry_run: | |
| description: "Write to content/_test/ instead of opening a PR" | |
| required: false | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: x-daily | |
| cancel-in-progress: false | |
| jobs: | |
| generate: | |
| runs-on: ubuntu-latest | |
| # A full week is ~42 posts (one nanocoder write each), so allow well beyond | |
| # the old daily budget. | |
| timeout-minutes: 180 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| # pin: actions/checkout@v4.3.1 | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up PNPM | |
| # pin: pnpm/action-setup@v4.4.0 | |
| uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10.11.0 | |
| - name: Use Node.js 22 | |
| # pin: actions/setup-node@v4.4.0 | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --no-frozen-lockfile | |
| - name: Build and install Nanocoder from main | |
| run: | | |
| git clone --depth 1 https://github.com/Nano-Collective/nanocoder.git /tmp/nanocoder | |
| cd /tmp/nanocoder | |
| # Nanocoder is a pnpm project: its lockfile and patchedDependencies | |
| # (e.g. the ink patch that adds Key.raw) are ignored by npm, so | |
| # build with the exact pnpm version it pins in packageManager. | |
| PNPM="$(node -p 'require("./package.json").packageManager')" | |
| npx --yes "$PNPM" install --frozen-lockfile | |
| npx --yes "$PNPM" run build | |
| mkdir -p /tmp/nanocoder-bin | |
| ln -sf /tmp/nanocoder/dist/cli.js /tmp/nanocoder-bin/nanocoder | |
| echo "/tmp/nanocoder-bin" >> "$GITHUB_PATH" | |
| /tmp/nanocoder-bin/nanocoder --version || true | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Ensure PR labels exist | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh label create x-daily --color "1f6feb" --description "Automated daily X-post PR" --force | |
| gh label create failed-validation --color "d93f0b" --description "Validator found hard-rule failures" --force | |
| - name: Fetch reference docs | |
| run: pnpm fetch-refs | |
| - name: Resolve date (the week's Monday) | |
| id: resolve | |
| env: | |
| INPUT_DATE: ${{ inputs.date }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${INPUT_DATE:-}" ]; then | |
| DATE="$INPUT_DATE" | |
| else | |
| # Monday of the current UTC week (%u: Mon=1..Sun=7). | |
| DOW=$(date -u +%u) | |
| DATE=$(date -u -d "-$((DOW - 1)) days" +%F) | |
| fi | |
| echo "date=$DATE" >> "$GITHUB_OUTPUT" | |
| echo "Resolved week-of Monday: $DATE" | |
| - name: Generate, validate, and open a PR | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| DATE: ${{ steps.resolve.outputs.date }} | |
| # Skip the model-context probe to models.dev for unknown providers. | |
| NANOCODER_CONTEXT_LIMIT: "128000" | |
| run: | | |
| set -euo pipefail | |
| BRANCH="auto/x-daily-${DATE}" | |
| PACK_DIR="content/_x-daily/${DATE}" | |
| POSTS_DIR="${PACK_DIR}/posts" | |
| # Skip if a branch already exists for this date — prevents duplicate | |
| # PRs on retried runs. | |
| if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then | |
| echo "branch $BRANCH already exists on origin, skipping" | |
| exit 0 | |
| fi | |
| git checkout -B "$BRANCH" origin/main | |
| if [ "${DRY_RUN:-false}" = "true" ]; then | |
| pnpm x-daily --date "$DATE" --test | |
| echo "dry_run: skipping PR open" | |
| exit 0 | |
| fi | |
| # Run generation. Validation failures (posts present but rules broken) | |
| # are NOT system failures — we still open a PR with failed-validation. | |
| # Generation crashes (Nanocoder error, network, etc.) ARE system | |
| # failures — clean up and surface them as red workflow status. | |
| set +e | |
| pnpm x-daily --date "$DATE" --commit | |
| GEN_STATUS=$? | |
| set -e | |
| # Discriminator: did the writers actually produce any post files? | |
| POST_COUNT=$(find "$POSTS_DIR" -maxdepth 1 -name "*.md" 2>/dev/null | wc -l | tr -d ' ') | |
| if [ "${POST_COUNT:-0}" -lt 1 ]; then | |
| echo "::error::x-daily produced no post files for ${DATE} — system failure" | |
| rm -rf "$PACK_DIR" | |
| git checkout main | |
| git branch -D "$BRANCH" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| # Stage the bucket plus the ledger (the script appends to it on a | |
| # clean pass) and commit. | |
| git add content/_x-daily/ | |
| if [ -z "$(git diff --cached --name-only)" ]; then | |
| echo "no content changes after add; skipping PR" | |
| git checkout main | |
| git branch -D "$BRANCH" 2>/dev/null || true | |
| exit 0 | |
| fi | |
| git commit -m "auto: x-daily posts ${DATE}" | |
| git push --set-upstream origin "$BRANCH" | |
| META_PATH="${PACK_DIR}/meta.json" | |
| FINAL_STATUS=$(jq -r '.final_status // "unknown"' "$META_PATH" 2>/dev/null || echo "unknown") | |
| ATTEMPTS=$(jq -r '.attempts // 0' "$META_PATH" 2>/dev/null || echo "0") | |
| LABELS="x-daily" | |
| if [ "$FINAL_STATUS" != "passed" ] || [ "$GEN_STATUS" -ne 0 ]; then | |
| LABELS="x-daily,failed-validation" | |
| fi | |
| BODY=$(printf 'Automated weekly X batch for the week of **%s** (the evergreen pool the calendar planner draws from).\n\n- Final status: `%s`\n- Attempts: `%s`\n- Posts: `%s`\n\nValidation ran during generation; the `meta.json` in the bucket has the report. A reviewer should sanity-check the posts before merging; the planner schedules them across the week once merged.' \ | |
| "$DATE" "$FINAL_STATUS" "$ATTEMPTS" "$POST_COUNT") | |
| # Try to open the PR. If creation fails, the branch on origin is | |
| # orphaned — delete it so the next run isn't blocked by the guard. | |
| set +e | |
| gh pr create \ | |
| --base main \ | |
| --head "$BRANCH" \ | |
| --title "Weekly X posts: week of ${DATE}" \ | |
| --body "$BODY" \ | |
| --label "$LABELS" | |
| PR_STATUS=$? | |
| set -e | |
| if [ "$PR_STATUS" -ne 0 ]; then | |
| echo "::error::pr creation failed for ${DATE} — deleting remote branch to unblock retries" | |
| git push origin --delete "$BRANCH" 2>/dev/null || true | |
| git checkout main | |
| git branch -D "$BRANCH" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| git checkout main | |
| git branch -D "$BRANCH" 2>/dev/null || true |