---
phase: 01-scaffold
plan: "03"
type: execute
wave: 1
depends_on: []
files_modified:
  - .github/workflows/DuckDBVersionMonitor.yml
autonomous: true
requirements:
  - INFRA-03

must_haves:
  truths:
    - "A weekly cron job polls GitHub API for the latest DuckDB release"
    - "When a new release is found and build passes, a version-bump PR is opened automatically"
    - "When a new release is found and build fails, a breakage PR is opened mentioning @copilot to fix it"
    - "The workflow can be triggered manually via workflow_dispatch for testing"
    - "No PR is opened when the current pin already matches the latest release"
  artifacts:
    - path: ".github/workflows/DuckDBVersionMonitor.yml"
      provides: "Scheduled DuckDB version monitor with conditional PR creation"
      contains: "DuckDBVersionMonitor"
  key_links:
    - from: ".github/workflows/DuckDBVersionMonitor.yml"
      to: "github.com/duckdb/duckdb releases API"
      via: "gh api repos/duckdb/duckdb/releases/latest"
      pattern: "gh api.*releases/latest"
    - from: ".github/workflows/DuckDBVersionMonitor.yml"
      to: "peter-evans/create-pull-request@v7"
      via: "conditional PR creation on success/failure"
      pattern: "create-pull-request"
    - from: ".github/workflows/DuckDBVersionMonitor.yml"
      to: "Makefile TARGET_DUCKDB_VERSION"
      via: "sed update of version pin on success"
      pattern: "TARGET_DUCKDB_VERSION"
---

<objective>
Create the scheduled DuckDB version monitoring workflow. This GitHub Actions workflow runs weekly, detects new DuckDB releases, attempts a build, and opens a PR regardless of success or failure — a version-bump PR when the build passes, or a breakage PR mentioning @copilot when it fails.

Purpose: DuckDB extensions are ABI-locked to the exact minor version they were compiled against (USE_UNSTABLE_C_API=1). Without automated version monitoring, a new DuckDB release will silently break the extension for users who upgrade. This workflow ensures the team is never blindsided.
Output: .github/workflows/DuckDBVersionMonitor.yml — a standalone workflow file.
</objective>

<execution_context>
@/Users/paul/.claude/get-shit-done/workflows/execute-plan.md
@/Users/paul/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-scaffold/01-RESEARCH.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create DuckDB version monitor workflow</name>
  <files>
    .github/workflows/DuckDBVersionMonitor.yml
  </files>
  <action>
Create `.github/workflows/DuckDBVersionMonitor.yml` implementing the full version monitoring logic per RESEARCH.md Pattern 4 and the locked decisions from CONTEXT.md.

**Locked requirements:**
- Weekly cron trigger (Monday 09:00 UTC)
- `workflow_dispatch` for manual testing
- Polls GitHub API for latest DuckDB release tag
- Compares to current pinned version in Makefile
- If new version found: bumps pin in Makefile and MainDistributionPipeline.yml, builds, then:
  - Build passes → opens version-bump PR (no @copilot needed)
  - Build fails → opens breakage PR with @copilot mention and build log link
- If already on latest: skips all steps silently (no PR, no noise)

Write the workflow file:

```yaml
name: DuckDB Version Monitor
on:
  schedule:
    - cron: '0 9 * * 1'  # Weekly, Monday 09:00 UTC
  workflow_dispatch:       # Allow manual trigger for testing

permissions:
  contents: write
  pull-requests: write

jobs:
  check-and-update:
    name: Check for new DuckDB release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Get latest DuckDB release
        id: latest
        run: |
          LATEST=$(gh api repos/duckdb/duckdb/releases/latest --jq '.tag_name')
          CURRENT=$(grep 'TARGET_DUCKDB_VERSION' Makefile | head -1 | cut -d'=' -f2 | tr -d ' ')
          echo "latest=$LATEST" >> $GITHUB_OUTPUT
          echo "current=$CURRENT" >> $GITHUB_OUTPUT
          if [ "$LATEST" != "$CURRENT" ]; then
            echo "is_new=true" >> $GITHUB_OUTPUT
          else
            echo "is_new=false" >> $GITHUB_OUTPUT
          fi
          echo "Latest DuckDB: $LATEST (current pin: $CURRENT)"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Skip if already up to date
        if: steps.latest.outputs.is_new == 'false'
        run: echo "Already on latest DuckDB ${{ steps.latest.outputs.current }}. No action needed."

      - name: Update version pin
        if: steps.latest.outputs.is_new == 'true'
        run: |
          LATEST="${{ steps.latest.outputs.latest }}"
          # Update Makefile version pin
          sed -i "s/TARGET_DUCKDB_VERSION=.*/TARGET_DUCKDB_VERSION=${LATEST}/" Makefile
          # Update MainDistributionPipeline.yml duckdb_version field
          sed -i "s/duckdb_version: .*/duckdb_version: ${LATEST}/" .github/workflows/MainDistributionPipeline.yml
          # Update PullRequestCI.yml duckdb_version field
          sed -i "s/duckdb_version: .*/duckdb_version: ${LATEST}/" .github/workflows/PullRequestCI.yml
          echo "Updated version pin to ${LATEST}"

      - name: Install Rust stable
        if: steps.latest.outputs.is_new == 'true'
        uses: dtolnay/rust-toolchain@stable

      - name: Build and test against new DuckDB version
        if: steps.latest.outputs.is_new == 'true'
        id: build
        run: make configure && make test_release
        continue-on-error: true

      - name: Open version-bump PR (build passed)
        if: steps.latest.outputs.is_new == 'true' && steps.build.outcome == 'success'
        uses: peter-evans/create-pull-request@v7
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          title: "chore: bump DuckDB pin to ${{ steps.latest.outputs.latest }}"
          body: |
            Automated version bump to DuckDB ${{ steps.latest.outputs.latest }}.

            Build and tests passed on Linux x86_64. The full platform matrix will run once this PR merges to `main`.

            ---
            *Generated by DuckDB Version Monitor workflow.*
          branch: "chore/duckdb-bump-${{ steps.latest.outputs.latest }}"
          commit-message: "chore: bump DuckDB pin to ${{ steps.latest.outputs.latest }}"
          delete-branch: true

      - name: Open breakage PR (build failed)
        if: steps.latest.outputs.is_new == 'true' && steps.build.outcome == 'failure'
        uses: peter-evans/create-pull-request@v7
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          title: "fix: DuckDB ${{ steps.latest.outputs.latest }} broke the build"
          body: |
            Build against DuckDB ${{ steps.latest.outputs.latest }} failed.

            @copilot please update the DuckDB version pin and fix any compilation errors.

            Build log: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

            ---
            *Generated by DuckDB Version Monitor workflow.*
          branch: "fix/duckdb-breakage-${{ steps.latest.outputs.latest }}"
          commit-message: "chore: attempt bump to DuckDB ${{ steps.latest.outputs.latest }}"
          delete-branch: true
```

**Important implementation notes:**

1. The `permissions: contents: write` and `permissions: pull-requests: write` are required for `peter-evans/create-pull-request` to work. Without these, the action fails with a 403 error.

2. The `is_new` comparison uses shell `[ "$LATEST" != "$CURRENT" ]` rather than a complex script. The CURRENT version is extracted from the Makefile `TARGET_DUCKDB_VERSION` variable — verify the exact variable name in the Makefile created in plan 02.

3. `continue-on-error: true` on the build step is critical — without it, a build failure terminates the job before the breakage PR step can run.

4. Both PR steps use `if: steps.build.outcome == 'success'` / `if: steps.build.outcome == 'failure'` (not `steps.build.conclusion`). The `outcome` property reflects the actual result after `continue-on-error`, while `conclusion` would be `success` even after failure when `continue-on-error: true`.

5. The `@copilot` mention in the breakage PR body is intentional — it requests automated fix attempts per the locked decision in CONTEXT.md.

After writing the file, validate the YAML is syntactically correct:
```bash
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/DuckDBVersionMonitor.yml'))" 2>&1
```
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views &amp;&amp; python3 -c "import yaml; yaml.safe_load(open('.github/workflows/DuckDBVersionMonitor.yml')); print('YAML valid')" 2>&amp;1</automated>
    <manual>
      Review the workflow file:
      1. `schedule: cron` is set to weekly Monday 09:00 UTC
      2. `permissions` block includes contents: write and pull-requests: write
      3. `continue-on-error: true` is on the build step
      4. Both PR creation steps use `steps.build.outcome` (not conclusion)
      5. Breakage PR body contains "@copilot please update the DuckDB version pin"
      6. Success PR body does NOT mention @copilot
    </manual>
  </verify>
  <done>.github/workflows/DuckDBVersionMonitor.yml exists. YAML is syntactically valid. Workflow has both success and failure PR creation paths. Breakage PR body contains "@copilot" mention. `continue-on-error: true` is present on the build step. `permissions` block is present.</done>
</task>

</tasks>

<verification>
After task completes:

1. `cat /Users/paul/Documents/Dev/Personal/duckdb-semantic-views/.github/workflows/DuckDBVersionMonitor.yml` — review structure
2. `python3 -c "import yaml; yaml.safe_load(open('/Users/paul/Documents/Dev/Personal/duckdb-semantic-views/.github/workflows/DuckDBVersionMonitor.yml')); print('YAML valid')"` — validates YAML syntax
3. `grep "@copilot" /Users/paul/Documents/Dev/Personal/duckdb-semantic-views/.github/workflows/DuckDBVersionMonitor.yml` — must match (in breakage PR body)
4. `grep "continue-on-error" /Users/paul/Documents/Dev/Personal/duckdb-semantic-views/.github/workflows/DuckDBVersionMonitor.yml` — must match
5. `grep "permissions" /Users/paul/Documents/Dev/Personal/duckdb-semantic-views/.github/workflows/DuckDBVersionMonitor.yml` — must match
</verification>

<success_criteria>
- .github/workflows/DuckDBVersionMonitor.yml exists and is valid YAML
- Workflow triggers on weekly cron (Monday 09:00 UTC) and workflow_dispatch
- Workflow has `permissions: contents: write, pull-requests: write`
- Build step has `continue-on-error: true`
- Success path opens a clean version-bump PR (no @copilot mention)
- Failure path opens a breakage PR with "@copilot please update the DuckDB version pin" in body
- Both PR steps use `steps.build.outcome` not `steps.build.conclusion`
</success_criteria>

<output>
After completion, create `.planning/phases/01-scaffold/01-03-SUMMARY.md` following the template at @/Users/paul/.claude/get-shit-done/templates/summary.md
</output>
