Spike: Renovate PR body formats, including batched PRs #2

Closed
opened 2026-10-01 21:55:15 -05:00 by deadstyle · 1 comment
Owner

Problem

The ★ Renovate view and the per-package display on PR rows ("postgres 16.4 → 17.0", bump type) depend on extracting package, from, to and update type from Renovate PRs. Renovate only exposes this inside the PR body's markdown, and our shared preset batches non-major updates into one PR per ecosystem ("Go non-major", "Docker images non-major"), so one PR can carry many packages.

Plan

Time-boxed investigation (half a day):

  1. Collect real Renovate PR bodies from homelab repos on git.bobparsons.dev, covering: docker / docker-compose, gomod, github-actions (Forgejo workflows), terraform/opentofu, npm, a custom.regex manager, a single-package major, a batched non-major group, a digest/pin update.
  2. Save them as fixtures under internal/core/testdata/renovate/.
  3. Document the table format(s) and the variations: column names per manager, how digests and pins render, multi-row groups, links in cells.
  4. Recommend a parsing approach and its failure mode (unparseable body → fall back to the PR title, the row still shows).

Plan-review additions (#17)

  • Batched PRs are the norm under our preset, so the fixtures must include several multi-package bodies (Go non-major, Docker non-major), not just one.
  • For every row, record what identifies the dependency beyond its name: the manager / datasource (ecosystem) and the package name as Renovate prints it. Grouping across repos keys on ecosystem + package + from + to, not on the display name.
  • Note which bodies are ambiguous (missing columns, truncated tables, a row we can't split). Those PRs must stay out of automatic grouping.

Deliverable

Fixtures committed, plus a comment here with the format notes and the recommended approach, decided by Opus via engineering-decision (ADR if it ends up shaping the domain model, e.g. RenovateUpdate becoming a list).

Context

  • docs/design.md → "Renovate features" and the ★ Renovate boxes.
  • docs/architecture.md → RenovateUpdate in the domain model sketch is a single struct; batched PRs mean it likely needs to be a slice.
  • Shared preset: deadstyle/forgejo → renovate/default.json (batches non-majors weekly, separates majors and breaking updates).
  • Product question raised by batching is tracked in the open product-decisions ticket.

Out of scope

Grouping logic and UI.

## Problem The ★ Renovate view and the per-package display on PR rows ("postgres 16.4 → 17.0", bump type) depend on extracting package, from, to and update type from Renovate PRs. Renovate only exposes this inside the PR body's markdown, and our shared preset batches non-major updates into one PR per ecosystem ("Go non-major", "Docker images non-major"), so one PR can carry many packages. ## Plan Time-boxed investigation (half a day): 1. Collect real Renovate PR bodies from homelab repos on git.bobparsons.dev, covering: docker / docker-compose, gomod, github-actions (Forgejo workflows), terraform/opentofu, npm, a custom.regex manager, a single-package major, a batched non-major group, a digest/pin update. 2. Save them as fixtures under `internal/core/testdata/renovate/`. 3. Document the table format(s) and the variations: column names per manager, how digests and pins render, multi-row groups, links in cells. 4. Recommend a parsing approach and its failure mode (unparseable body → fall back to the PR title, the row still shows). ### Plan-review additions (#17) - Batched PRs are the norm under our preset, so the fixtures must include several multi-package bodies (Go non-major, Docker non-major), not just one. - For every row, record what identifies the dependency beyond its name: the manager / datasource (ecosystem) and the package name as Renovate prints it. Grouping across repos keys on ecosystem + package + from + to, not on the display name. - Note which bodies are ambiguous (missing columns, truncated tables, a row we can't split). Those PRs must stay out of automatic grouping. ## Deliverable Fixtures committed, plus a comment here with the format notes and the recommended approach, decided by Opus via `engineering-decision` (ADR if it ends up shaping the domain model, e.g. `RenovateUpdate` becoming a list). ## Context - `docs/design.md` → "Renovate features" and the ★ Renovate boxes. - `docs/architecture.md` → `RenovateUpdate` in the domain model sketch is a single struct; batched PRs mean it likely needs to be a slice. - Shared preset: `deadstyle/forgejo` → `renovate/default.json` (batches non-majors weekly, separates majors and breaking updates). - Product question raised by batching is tracked in the open product-decisions ticket. ## Out of scope Grouping logic and UI.
Author
Owner

🤖 Spike result. Fixtures on spike/2-renovate-bodies (a327569, 83f13dc) in internal/core/testdata/renovate/, with format notes in its README. MR to follow once SSH on :2222 is back.

What exists: the bot is renovate-bot (Renovate 44.115.13), with 17 PRs (all closed) and 13 dashboards. The fixtures cover docker-compose majors, github-actions majors, a one-member non-major group, and one PR with two rows from two managers. No PR exists yet for gomod, terraform, npm, custom.regex, digest/pin, or a batched multi-package group. The only evidence of batching is a pending dashboard entry in traefik.

Format:

  • The table follows This PR contains the following updates:. There are two header shapes: Package | Update | Change, and Package | Type | Update | Change, where Type is the dependency type (action, uses-with, final), not the manager.
  • The package cell is [name](homepage), optionally followed by ([source](url)). The homepage isn't an identity, because two images both link forgejo.org.
  • The change cell is from` → `to with raw tags.
  • The trailing <!--renovate-debug:…--> decodes to versions, target branch and configured labels only. It has no package data.
  • Group PRs with one member get the single-update title. Only the branch (renovate/<group-slug> vs renovate/<slug>-N.x) reveals that the PR is a group.
  • Adapter gotcha: on closed PRs whose branch was deleted, head.ref becomes refs/pull/N/head, and the branch survives only in head.label.

Decided (Opus): parsing. Take the first table after the intro line, map cells by header name, and stop at the first non-table line. Reject the whole body on a missing intro, a column-count mismatch, an unknown column, or a change cell that isn't x → y. A rejected PR shows with its plain title, outside grouping. A throwaway parser handled all 8 PR fixtures and rejected all 3 dashboards.

Decided (Opus): shape for #4. Each PR gets a list of updates, one per table row: ecosystem, package, dep type, update type, from, to, and source URL. Ecosystem is inferred from the Type column or the title noun. If it's unknown, the row stays out of automatic grouping (the two node rows in forgejo#9 show why the name alone is ambiguous). This lands in #4's ADR.

Gap: there are no batched or digest fixtures. #10 should add hand-written fixtures from Renovate's documented format before relying on those paths, and the reject-on-doubt rule covers them until then.

Product question raised: should actions/checkout v3→v7 in one repo and v4→v7 in another be one row in Updates by dependency? Added as item 13 on #13.

🤖 Spike result. Fixtures on `spike/2-renovate-bodies` (a327569, 83f13dc) in `internal/core/testdata/renovate/`, with format notes in its README. MR to follow once SSH on :2222 is back. **What exists:** the bot is `renovate-bot` (Renovate 44.115.13), with 17 PRs (all closed) and 13 dashboards. The fixtures cover docker-compose majors, github-actions majors, a one-member non-major group, and one PR with two rows from two managers. **No PR exists yet** for gomod, terraform, npm, custom.regex, digest/pin, or a batched multi-package group. The only evidence of batching is a pending dashboard entry in traefik. **Format:** - The table follows `This PR contains the following updates:`. There are two header shapes: `Package | Update | Change`, and `Package | Type | Update | Change`, where `Type` is the dependency type (`action`, `uses-with`, `final`), not the manager. - The package cell is `[name](homepage)`, optionally followed by ` ([source](url))`. The homepage isn't an identity, because two images both link forgejo.org. - The change cell is ```from` → `to``` with raw tags. - The trailing `<!--renovate-debug:…-->` decodes to versions, target branch and configured labels only. It has no package data. - Group PRs with one member get the single-update title. Only the branch (`renovate/<group-slug>` vs `renovate/<slug>-N.x`) reveals that the PR is a group. - Adapter gotcha: on closed PRs whose branch was deleted, `head.ref` becomes `refs/pull/N/head`, and the branch survives only in `head.label`. **Decided (Opus): parsing.** Take the first table after the intro line, map cells by header name, and stop at the first non-table line. Reject the whole body on a missing intro, a column-count mismatch, an unknown column, or a change cell that isn't `x → y`. A rejected PR shows with its plain title, outside grouping. A throwaway parser handled all 8 PR fixtures and rejected all 3 dashboards. **Decided (Opus): shape for #4.** Each PR gets a list of updates, one per table row: ecosystem, package, dep type, update type, from, to, and source URL. Ecosystem is inferred from the Type column or the title noun. If it's unknown, the row stays out of automatic grouping (the two `node` rows in forgejo#9 show why the name alone is ambiguous). This lands in #4's ADR. **Gap:** there are no batched or digest fixtures. #10 should add hand-written fixtures from Renovate's documented format before relying on those paths, and the reject-on-doubt rule covers them until then. **Product question raised:** should `actions/checkout` v3→v7 in one repo and v4→v7 in another be one row in Updates by dependency? Added as item 13 on #13.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
deadstyle/lazyforge#2
No description provided.