From 5aaad0f4bf0a68d14d90bb5513063a10e4571962 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:21:19 +0000 Subject: [PATCH 1/4] chore: prepare release 0.3.3 Strip empty Unreleased section from release branch. Release date TBD (set during finalization). --- CHANGELOG.md | 14 -------------- assets/workspace/.devcontainer/CHANGELOG.md | 14 -------------- 2 files changed, 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b860d9f..b7136f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,20 +5,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased - -### Added - -### Changed - -### Deprecated - -### Removed - -### Fixed - -### Security - ## [0.3.3] - TBD ### Added diff --git a/assets/workspace/.devcontainer/CHANGELOG.md b/assets/workspace/.devcontainer/CHANGELOG.md index 2b860d9f..b7136f94 100644 --- a/assets/workspace/.devcontainer/CHANGELOG.md +++ b/assets/workspace/.devcontainer/CHANGELOG.md @@ -5,20 +5,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased - -### Added - -### Changed - -### Deprecated - -### Removed - -### Fixed - -### Security - ## [0.3.3] - TBD ### Added From 33e2720f645be14a0d5a413bff035663c2167075 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:54:01 +0000 Subject: [PATCH 2/4] chore: finalize release 0.3.3 Set release date to 2026-04-10 and regenerate release docs. Refs: #516 --- .vig-os | 2 +- CHANGELOG.md | 2 +- README.md | 2 +- assets/workspace/.devcontainer/CHANGELOG.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.vig-os b/.vig-os index 80f309b1..0e54bf9c 100644 --- a/.vig-os +++ b/.vig-os @@ -1,2 +1,2 @@ # vig-os devcontainer configuration -DEVCONTAINER_VERSION=0.3.2 +DEVCONTAINER_VERSION=0.3.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index b7136f94..c2e6f759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.3] - TBD +## [0.3.3](https://github.com/vig-os/devcontainer/releases/tag/0.3.3) - 2026-04-10 ### Added diff --git a/README.md b/README.md index 6c6bf42b..ef9325cf 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ For detailed command descriptions, run `just --list --unsorted` or `just --help` - **Registry**: `ghcr.io/vig-os/devcontainer` - **Architecture**: Multi-platform support (AMD64, ARM64) - **License**: Apache -- **Latest Version**: [0.3.2](https://github.com/vig-os/devcontainer/releases/tag/0.3.2) - 2026-04-08 +- **Latest Version**: [0.3.3](https://github.com/vig-os/devcontainer/releases/tag/0.3.3) - 2026-04-10 - **Image tags**: bare semver (`0.2.1`, `latest`) — git tags use `v` prefix (`v0.2.1`) but image tags do not ## Features diff --git a/assets/workspace/.devcontainer/CHANGELOG.md b/assets/workspace/.devcontainer/CHANGELOG.md index b7136f94..c2e6f759 100644 --- a/assets/workspace/.devcontainer/CHANGELOG.md +++ b/assets/workspace/.devcontainer/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.3] - TBD +## [0.3.3](https://github.com/vig-os/devcontainer/releases/tag/0.3.3) - 2026-04-10 ### Added From f99026e847c893ab230c333d688de1815c9cebed Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:54:34 +0000 Subject: [PATCH 3/4] chore: sync issues and PRs --- docs/issues/issue-511.md | 66 +++ docs/issues/issue-512.md | 29 ++ docs/pull-requests/pr-513.md | 815 +++++++++++++++++++++++++++++++++++ docs/pull-requests/pr-514.md | 92 ++++ docs/pull-requests/pr-515.md | 173 ++++++++ docs/pull-requests/pr-516.md | 67 +++ 6 files changed, 1242 insertions(+) create mode 100644 docs/issues/issue-511.md create mode 100644 docs/issues/issue-512.md create mode 100644 docs/pull-requests/pr-513.md create mode 100644 docs/pull-requests/pr-514.md create mode 100644 docs/pull-requests/pr-515.md create mode 100644 docs/pull-requests/pr-516.md diff --git a/docs/issues/issue-511.md b/docs/issues/issue-511.md new file mode 100644 index 00000000..04ab0eb3 --- /dev/null +++ b/docs/issues/issue-511.md @@ -0,0 +1,66 @@ +--- +type: issue +state: closed +created: 2026-04-10T06:32:05Z +updated: 2026-04-10T13:19:49Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/511 +comments: 0 +labels: feature, area:ci +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-04-10T13:54:27.274Z +--- + +# [Issue 511]: [[FEATURE] Smoke-test dispatch: trigger promote-release for final; wait on CI for RC](https://github.com/vig-os/devcontainer/issues/511) + +## Description + +Update the smoke-test `repository-dispatch` workflow so **final** releases dispatch downstream `promote-release.yml` (publish release, merge PR, cleanup RC tags) instead of only merging the release PR; and so **RC** triggers verify release-PR CI but **do not** merge to `main`. + +## Problem statement + +- Issue #507 / PR #510 fixed draft-release handling in `promote-release.yml`. +- Today `assets/smoke-test/.github/workflows/repository-dispatch.yml` runs `merge-release-pr` for **both** RC and final: it enables auto-merge and polls. +- Downstream `release.yml` creates a **draft** final release; nobody publishes it, so the release stays draft. +- Upstream `.github/workflows/promote-release.yml` requires a **published** (non-draft, non-prerelease) downstream final release before it proceeds — so automation fails unless someone publishes manually. +- For RC, merging the release PR to `main` on every candidate is unnecessary; the branch is recreated on the next dispatch. + +## Proposed solution + +Behavior split by `release_kind`: + +**Common path (RC + final):** existing steps through `trigger-release` unchanged in intent; then replace terminal merge behavior as below. + +1. **Remove** the `merge-release-pr` job. +2. **Add** `wait-release-pr-ci` (RC + final): after `trigger-release`, poll required checks on the release PR (`gh pr checks`); succeed when green; fail on failure or timeout (e.g. 30 min). For RC this is terminal — PR stays open. +3. **Add** `trigger-promote-release` (**final only**): `if: needs.validate.outputs.release_kind == 'final'`; repository-dispatch `promote-release.yml` on `dev` with `version=$BASE_VERSION`; same dispatch-and-poll pattern as `trigger-release` / `trigger-prepare-release`. Downstream `promote-release` validates draft + PR, publishes release, merges PR, cleans RC tags. +4. **Update** `summary` and `notify-failure`: depend on `wait-release-pr-ci` and `trigger-promote-release`; treat skipped `trigger-promote-release` on RC as success, not failure. + +**Docs:** Update `docs/CROSS_REPO_RELEASE_GATE.md` — receiver triggers `promote-release` for final; RC path no longer merges. + +**Scope:** `assets/smoke-test/.github/workflows/repository-dispatch.yml` + `docs/CROSS_REPO_RELEASE_GATE.md`. Do **not** change upstream or workspace-template `promote-release.yml` in this issue. + +## Alternatives considered + +- Duplicate publish/merge/cleanup logic in `repository-dispatch.yml` — rejected (DRY; dogfood `promote-release` template). + +## Additional context + +- Workspace template already ships `assets/workspace/.github/workflows/promote-release.yml` via `init-workspace.sh`. +- **Prerequisite:** PR #510 merged and deployed to the smoke-test repo before exercising this change end-to-end. +- Related: #507 (draft release API), #510 (fix). + +## Impact + +- Unblocks upstream `promote-release` by publishing the downstream final release automatically. +- RC flow stops merging prematurely; aligns with cross-repo gate contract for pre-releases. + +**Changelog category:** Changed + +- [ ] TDD compliance (see `.cursor/rules/tdd.mdc`) + diff --git a/docs/issues/issue-512.md b/docs/issues/issue-512.md new file mode 100644 index 00000000..80ad25fe --- /dev/null +++ b/docs/issues/issue-512.md @@ -0,0 +1,29 @@ +--- +type: issue +state: closed +created: 2026-04-10T06:54:22Z +updated: 2026-04-10T13:20:01Z +author: github-actions[bot] +author_url: https://github.com/github-actions[bot] +url: https://github.com/vig-os/devcontainer/issues/512 +comments: 0 +labels: security, security-scan +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-04-10T13:54:26.944Z +--- + +# [Issue 512]: [Nightly security scan: HIGH/CRITICAL vulnerabilities in :latest](https://github.com/vig-os/devcontainer/issues/512) + +Nightly scan found **fixable HIGH/CRITICAL** vulnerabilities in the resolved image below (after `.trivyignore`). + +- **Image (resolved):** `ghcr.io/vig-os/devcontainer@sha256:b59c4b5ee13f06729400516309255f011b456e1e2001ca11fabb0ebefa4e2416` +- **Tag pulled:** `ghcr.io/vig-os/devcontainer:latest` +- **Scan date (UTC):** 2026-04-10T06:54:20Z +- **Workflow run:** https://github.com/vig-os/devcontainer/actions/runs/24230540059 +- **Security tab:** https://github.com/vig-os/devcontainer/security + +Close this issue after the image is remediated and the next scheduled run passes the gate. diff --git a/docs/pull-requests/pr-513.md b/docs/pull-requests/pr-513.md new file mode 100644 index 00000000..f961f864 --- /dev/null +++ b/docs/pull-requests/pr-513.md @@ -0,0 +1,815 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/509-switch-dependabot-to-renovate → dev +created: 2026-04-10T09:00:18Z +updated: 2026-04-10T12:06:38Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/513 +comments: 16 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-04-10T12:06:37Z +synced: 2026-04-10T13:54:33.205Z +--- + +# [PR 513](https://github.com/vig-os/devcontainer/pull/513) chore: switch from Dependabot to Renovate and automate dependency changelog + +## Description + +Implements [#509](https://github.com/vig-os/devcontainer/issues/509) (switch from Dependabot to Renovate for dependency updates, including template paths Dependabot could not scan) and [#506](https://github.com/vig-os/devcontainer/issues/506) (automatic `CHANGELOG.md` updates on Renovate PRs via `pull_request_target` and `renovate-changelog-pr`). Adds `.vig-os` + `resolve-image` so `sync-issues` and `sync-main-to-dev` run in the pinned devcontainer image; `install.sh` / `init-workspace.sh` resolve `GITHUB_REPOSITORY` for templated `renovate.json`. + +## Type of Change + +- [x] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [x] `docs` -- Documentation only +- [x] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [x] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- **27 files**, ~1164 insertions / ~247 deletions vs `dev` (includes merge commit from `origin/dev`). +- **Renovate / Dependabot:** Remove root and workspace `dependabot.yml`; add `renovate.json`, `assets/workspace/renovate.json`, `renovate-default.json`, smoke-test `renovate.json`; update `CODEOWNERS`. +- **CI image pinning:** Add `.vig-os`, `.github/actions/resolve-image/`, `release.yml` step to bump `DEVCONTAINER_VERSION` on final release; `sync-issues.yml` / `sync-main-to-dev.yml` use resolved GHCR tag, `safe.directory`, drop `setup-env` / inlined retry; sync branch creation uses `git push`. +- **Workspace init:** `parse-github-remote-lib.sh`, `Containerfile` placeholder manifest, `install.sh --repo`, `init-workspace.sh` + BATS/install/conftest updates. +- **Changelog automation:** `renovate_changelog_pr.py`, tests, console script in `pyproject.toml`, root + workspace `renovate-changelog.yml`; `scripts/manifest.toml` syncs new paths. +- **Docs:** `CHANGELOG.md` and synced `assets/workspace/.devcontainer/CHANGELOG.md`. + +## Changelog Entry + +### Added + +- **Renovate changelog automation** ([#506](https://github.com/vig-os/devcontainer/issues/506)) + - `renovate-changelog-pr` CLI tool parses Renovate PR metadata and inserts Keep-a-Changelog entries under `## Unreleased` + - `renovate-changelog` workflow runs on `pull_request_target` for `renovate[bot]` PRs in both upstream and workspace template +- **Devcontainer image version pinning** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - `.vig-os` file at repo root declares `DEVCONTAINER_VERSION` as the single source of truth for CI container image tags + - `resolve-image` composite action resolves the image tag and validates it exists in GHCR +- **`GITHUB_REPOSITORY` resolution for workspace init** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - `parse-github-remote-lib.sh` extracts `owner/repo` from HTTPS, SSH, and `git@` GitHub URLs + - `install.sh` gains `--repo` flag; `init-workspace.sh` replaces `{{GITHUB_REPOSITORY}}` in workspace template files + +### Changed + +- **Switch from Dependabot to Renovate** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - Replace `.github/dependabot.yml` with `renovate.json` and shared `renovate-default.json` preset + - Renovate covers all ecosystems previously tracked (github-actions, pip, npm, docker) plus template directories not reachable by Dependabot +- **Sync workflows run in devcontainer image** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - `sync-issues` and `sync-main-to-dev` use `resolve-image` and run inside the pinned devcontainer, removing the `setup-env` composite action dependency and the inlined retry helper + - `sync-main-to-dev` creates sync branches via `git push` instead of the GitHub refs API + +### Removed + +- **Dependabot configuration** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - Delete `.github/dependabot.yml` and `assets/workspace/.github/dependabot.yml` + +## Testing + +- [x] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A (CI and scripted tests cover changes; Renovate/changelog workflow validates in GitHub after merge). + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [x] New and existing unit tests pass locally with my changes +- [x] Any dependent changes have been merged and published + +## Additional Notes + +- Branch merged latest `origin/dev` before PR (includes unrelated doc sync under `docs/pull-requests/`). +- Commit 6 subject is `docs:` (not `docs(changelog):`) due to commit-msg scope allowlist. + +Refs: #509, #506 + + + +--- +--- + +## Review Threads (8) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 09:12 AM_ + +_File: [`renovate.json (line 6 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063279375)_ + +```diff +@@ -0,0 +1,15 @@ ++{ ++ "$schema": "https://docs.renovatebot.com/renovate-schema.json", ++ "extends": [ ++ "github>vig-os/devcontainer//assets/workspace/.github/renovate-default" ++ ], ++ "enabledManagers": ["github-actions", "pep621", "uv", "npm", "dockerfile"], +``` + +`extends` points to `assets/workspace/.github/renovate-default` but the preset file in this repo is `assets/workspace/.github/renovate-default.json`. If Renovate treats this as a file path (not a preset name), this will 404 and disable updates. Consider referencing the exact filename (or whatever format Renovate expects for repo file presets) to avoid misresolution. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:43 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063978670) + + Renovate preset resolution automatically appends `.json` when no extension is given (`github>org/repo//path/name` resolves to `path/name.json`). No change needed; see https://github.com/renovatebot/renovate/blob/master/docs/usage/config-presets.md + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 09:12 AM_ + +_File: [`assets/workspace/renovate.json (line 6 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063279423)_ + +```diff +@@ -0,0 +1,7 @@ ++{ ++ "$schema": "https://docs.renovatebot.com/renovate-schema.json", ++ "extends": [ ++ "github>{{GITHUB_REPOSITORY}}//.github/renovate-default" ++ ], ++ "enabledManagers": ["github-actions", "pep621", "uv", "npm"] +``` + +The `extends` value uses `//.github/renovate-default`, but the preset in this template is `assets/workspace/.github/renovate-default.json`. If Renovate requires the explicit `.json` path, downstream workspaces will fail to load the preset. Consider matching the actual filename to avoid configuration resolution errors. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:43 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063978734) + + Same as the root `renovate.json` thread: Renovate resolves `//path/name` to `path/name.json` for repo presets. No change needed. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 09:12 AM_ + +_File: [`install.sh (line 368 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063279458)_ + +```diff +@@ -315,6 +342,21 @@ PROJECT_NAME=$(sanitize_name "$PROJECT_NAME") + # Sanitize ORG_NAME for security (remove shell metacharacters) but preserve capitalization + ORG_NAME=$(sanitize_for_security "$ORG_NAME") + ++# GITHUB_REPOSITORY for init-workspace --no-prompts (Renovate extends in renovate.json) ++GITHUB_REPOSITORY="$GITHUB_REPO_OVERRIDE" ++GITHUB_REPOSITORY=$(sanitize_for_security "$GITHUB_REPOSITORY") ++if [ -z "$GITHUB_REPOSITORY" ] && [ -d "$PROJECT_PATH/.git" ]; then ++ url=$(git -C "$PROJECT_PATH" remote get-url origin 2>/dev/null || true) ++ if [ -n "$url" ]; then ++ if repo=$(parse_github_remote "$url"); then ++ GITHUB_REPOSITORY="$repo" ++ fi ++ fi ++fi ++if [ -z "$GITHUB_REPOSITORY" ]; then ++ GITHUB_REPOSITORY="OWNER/REPO" ++fi +``` + +`parse_github_remote` can return owner/repo segments containing characters that are not valid in GitHub repo slugs (the regex allows anything except '/' and '.'). Since the parsed value is later passed through to init-workspace and used for template substitution, it should be validated/sanitized after parsing (e.g., enforce `^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$` or run `sanitize_for_security` on the parsed result as well). + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:43 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063978841) + + Addressed: `parse_github_remote` now validates `owner/repo` against `^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$`, and `install.sh` runs `sanitize_for_security` on the parsed value before passing it through (`eb9bb4a`, `0f3a4c0`). + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 09:12 AM_ + +_File: [`assets/parse-github-remote-lib.sh (line 32 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063279483)_ + +```diff +@@ -0,0 +1,53 @@ ++#!/usr/bin/env bash ++# Parse github.com remote URL to owner/repo (stdout), or return 1 if unsupported ++# Used by init-workspace.sh for {{GITHUB_REPOSITORY}} in renovate.json (Refs: #509). ++parse_github_remote() { ++ local url="$1" ++ [[ -z "$url" ]] && return 1 ++ # https://github.com/org/repo.git or trailing / ++ if [[ "$url" =~ https?://github\.com/([^/]+)/([^/.]+)(\.git)?/?$ ]]; then ++ echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" ++ return 0 ++ fi ++ # git@github.com:org/repo.git ++ if [[ "$url" =~ ^git@github\.com:([^/]+)/([^/.]+)(\.git)?$ ]]; then ++ echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" ++ return 0 ++ fi ++ # ssh://git@github.com/org/repo.git ++ if [[ "$url" =~ ^ssh://git@github\.com/([^/]+)/([^/.]+)(\.git)?$ ]]; then ++ echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" ++ return 0 ++ fi ++ return 1 +``` + +`parse_github_remote` matches `([^/.]+)` for owner/repo, which allows many characters GitHub does not permit in repository slugs (e.g., spaces, `$`, `;`). Since `GITHUB_REPOSITORY` is later used for placeholder substitution into files, consider tightening validation to GitHub’s allowed charset (or validating the final `owner/repo` matches an allowlist regex) before accepting it. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:43 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063978930) + + Implemented in `parse_github_remote`: after URL extraction we reject slugs outside GitHub's allowed charset (`eb9bb4a`). Added BATS coverage for the rejection path. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 09:12 AM_ + +_File: [`packages/vig-utils/src/vig_utils/renovate_changelog_pr.py (line 188 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063279505)_ + +```diff +@@ -0,0 +1,238 @@ ++"""Parse Renovate PR metadata and insert a Keep-a-Changelog entry (Refs: #506).""" ++ ++from __future__ import annotations ++ ++import argparse ++import os ++import re ++import sys ++from pathlib import Path ++ ++ ++def _strip_md_link(cell: str) -> str: ++ s = cell.strip() ++ m = re.match(r"\[([^\]]+)\]\([^)]+\)", s) ++ if m: ++ return m.group(1).strip() ++ return s ++ ++ ++def _parse_change_cell(cell: str) -> tuple[str | None, str | None]: ++ """Return (old, new) from a Renovate-style change cell.""" ++ text = cell.strip() ++ m = re.search(r"`([^`]+)`\s*->\s*`([^`]+)`", text) ++ if m: ++ return m.group(1).strip(), m.group(2).strip() ++ # Digest / unquoted: abc -> def ++ m = re.search(r"(\S+)\s*->\s*(\S+)", text) ++ if m: ++ return m.group(1).strip(), m.group(2).strip() ++ return None, None ++ ++ ++def _parse_table_updates(body: str) -> list[tuple[str, str | None, str | None]]: ++ rows: list[tuple[str, str | None, str | None]] = [] ++ for raw_line in body.splitlines(): ++ line = raw_line.strip() ++ if not line.startswith("|"): ++ continue ++ parts = [p.strip() for p in line.split("|")] ++ # leading/trailing empty from split ++ cells = [c for c in parts if c != ""] ++ if len(cells) < 2: ++ continue ++ if re.match(r"^-+$", cells[0].replace(" ", "")): ++ continue ++ first = _strip_md_link(cells[0]) ++ if first.lower() in ("package", "name", "dependency"): ++ continue ++ old_v, new_v = None, None ++ for cell in reversed(cells): ++ o, n = _parse_change_cell(cell) ++ if o is not None and n is not None: ++ old_v, new_v = o, n ++ break ++ if new_v is not None: ++ rows.append((first, old_v, new_v)) ++ return rows ++ ++ ++def _parse_title_updates(title: str) -> list[tuple[str, str | None, str | None]]: ++ t = title.strip() ++ # digest: update actions/checkout digest to ++ m = re.search( ++ r"update\s+([^\s]+)\s+digest\s+to\s+(\S+)", ++ t, ++ re.IGNORECASE, ++ ) ++ if m: ++ return [(m.group(1), None, m.group(2))] ++ # update dependency to ++ m = re.search( ++ r"update\s+dependency\s+(\S+)\s+to\s+(\S+)", ++ t, ++ re.IGNORECASE, ++ ) ++ if m: ++ return [(m.group(1), None, m.group(2))] ++ # update to (no "dependency") ++ m = re.search(r"update\s+(\S+)\s+to\s+(\S+)", t, re.IGNORECASE) ++ if m: ++ return [(m.group(1), None, m.group(2))] ++ return [] ++ ++ ++def parse_renovate_pr_updates( ++ title: str, body: str ++) -> list[tuple[str, str | None, str | None]]: ++ from_table = _parse_table_updates(body) ++ if from_table: ++ return from_table ++ return _parse_title_updates(title) ++ ++ ++def format_changelog_entry( ++ pr_number: int, ++ repo_html_url: str, ++ updates: list[tuple[str, str | None, str | None]], ++) -> str: ++ base = repo_html_url.rstrip("/") ++ pr_url = f"{base}/pull/{pr_number}" ++ pr_link = f"([#{pr_number}]({pr_url}))" ++ if len(updates) == 1: ++ pkg, old_v, new_v = updates[0] ++ if old_v: ++ title = f"Renovate: update `{pkg}` from `{old_v}` to `{new_v}`" ++ else: ++ title = f"Renovate: update `{pkg}` to `{new_v}`" ++ return f"- **{title}** {pr_link}\n" ++ lines = [f"- **Renovate dependency update** {pr_link}"] ++ for pkg, old_v, new_v in updates: ++ if old_v: ++ lines.append(f" - Update `{pkg}` from `{old_v}` to `{new_v}`") ++ else: ++ lines.append(f" - Update `{pkg}` to `{new_v}`") ++ return "\n".join(lines) + "\n" ++ ++ ++def _pr_marked_in_changed(unreleased: str, pr_number: int) -> bool: ++ needle = f"[#{pr_number}](" ++ changed_idx = unreleased.find("### Changed") ++ if changed_idx == -1: ++ return False ++ next_hdr = re.search(r"\n### (?!Changed)\w+", unreleased[changed_idx:]) ++ if next_hdr: ++ changed_block = unreleased[changed_idx : changed_idx + next_hdr.start()] ++ else: ++ changed_block = unreleased[changed_idx:] ++ return needle in changed_block ++ ++ ++def insert_renovate_changelog_entry( ++ changelog: str, ++ pr_number: int, ++ entry: str, ++) -> tuple[str, bool]: ++ lines = changelog.splitlines(keepends=True) ++ unreleased_start: int | None = None ++ unreleased_end: int | None = None ++ for i, line in enumerate(lines): ++ if line.startswith("## Unreleased"): ++ unreleased_start = i ++ break ++ if unreleased_start is None: ++ return changelog, False ++ for j in range(unreleased_start + 1, len(lines)): ++ if lines[j].startswith("## [") and "[Unreleased]" not in lines[j]: ++ unreleased_end = j ++ break ++ if unreleased_end is None: ++ unreleased_end = len(lines) ++ block = "".join(lines[unreleased_start:unreleased_end]) ++ if _pr_marked_in_changed(block, pr_number): ++ return changelog, False ++ ++ changed_idx: int | None = None ++ next_sec: int | None = None ++ for i in range(unreleased_start, unreleased_end): ++ line = lines[i] ++ if line.startswith("### Changed"): ++ changed_idx = i ++ continue ++ if ( ++ changed_idx is not None ++ and line.startswith("### ") ++ and not line.startswith("### Changed") ++ ): ++ next_sec = i ++ break ++ if changed_idx is None: ++ return changelog, False ++ ++ # Insert before the next ### heading, or at end of ## Unreleased if Changed is last subsection ++ insert_at = next_sec if next_sec is not None else unreleased_end ++ ++ if not entry.endswith("\n"): ++ entry = entry + "\n" ++ new_lines = lines[:insert_at] + [entry] + lines[insert_at:] ++ return "".join(new_lines), True +``` + +`insert_renovate_changelog_entry` inserts `entry` immediately before the next `###` heading (or `##` version), which can remove the blank line that currently separates list items from the following heading (Keep-a-Changelog formatting). Consider inserting before trailing blank line(s) that precede the next heading, or ensuring the inserted entry is followed by a blank line when the next line is a heading. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:43 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3063978997) + + Fixed by moving the insertion point backward across blank lines before the next heading so the Keep-a-Changelog separator stays intact; regression test in `acb4b4a`, fix in `294c1d4`. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 11:50 AM_ + +_File: [`install.sh (line 255 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3064025353)_ + +```diff +@@ -221,6 +224,26 @@ sanitize_for_security() { + echo "$1" | sed 's/[^a-zA-Z0-9._\/-]/_/g' + } + ++# Parse github.com remote URL to owner/repo (stdout), or return 1 if unsupported ++# Same rules as assets/parse-github-remote-lib.sh (for GITHUB_REPOSITORY / renovate.json). ++parse_github_remote() { ++ local url="$1" ++ [[ -z "$url" ]] && return 1 ++ if [[ "$url" =~ https?://github\.com/([^/]+)/([^/.]+)(\.git)?/?$ ]]; then ++ echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" ++ return 0 ++ fi ++ if [[ "$url" =~ ^git@github\.com:([^/]+)/([^/.]+)(\.git)?$ ]]; then ++ echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" ++ return 0 ++ fi ++ if [[ "$url" =~ ^ssh://git@github\.com/([^/]+)/([^/.]+)(\.git)?$ ]]; then ++ echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" ++ return 0 ++ fi ++ return 1 ++} +``` + +In install.sh, parse_github_remote claims to follow the same rules as assets/parse-github-remote-lib.sh, but it currently returns the extracted owner/repo without validating it against the allowed GitHub slug charset. This means values like 'or;g/re$po' could be accepted (and merely transformed by sanitize_for_security), producing a non-existent repo and potentially breaking Renovate preset resolution. Align this helper with the library by enforcing the same allowlist regex (^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$) and failing when it doesn't match (or reuse the shared library). + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:59 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3064093573) + + Addressed in 852d77a: `parse_github_remote` in `install.sh` now applies the same `owner/repo` allowlist as `assets/parse-github-remote-lib.sh` before echoing a parsed remote. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 11:50 AM_ + +_File: [`assets/parse-github-remote-lib.sh (line 71 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3064025402)_ + +```diff +@@ -0,0 +1,63 @@ ++#!/usr/bin/env bash ++# Parse github.com remote URL to owner/repo (stdout), or return 1 if unsupported ++# Used by init-workspace.sh for {{GITHUB_REPOSITORY}} in renovate.json (Refs: #509). ++parse_github_remote() { ++ local url="$1" ++ local owner repo ++ [[ -z "$url" ]] && return 1 ++ # https://github.com/org/repo.git or trailing / ++ if [[ "$url" =~ https?://github\.com/([^/]+)/([^/.]+)(\.git)?/?$ ]]; then ++ owner="${BASH_REMATCH[1]}" ++ repo="${BASH_REMATCH[2]}" ++ [[ "$owner/$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || return 1 ++ echo "$owner/$repo" ++ return 0 ++ fi ++ # git@github.com:org/repo.git ++ if [[ "$url" =~ ^git@github\.com:([^/]+)/([^/.]+)(\.git)?$ ]]; then ++ owner="${BASH_REMATCH[1]}" ++ repo="${BASH_REMATCH[2]}" ++ [[ "$owner/$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || return 1 ++ echo "$owner/$repo" ++ return 0 ++ fi ++ # ssh://git@github.com/org/repo.git ++ if [[ "$url" =~ ^ssh://git@github\.com/([^/]+)/([^/.]+)(\.git)?$ ]]; then ++ owner="${BASH_REMATCH[1]}" ++ repo="${BASH_REMATCH[2]}" ++ [[ "$owner/$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || return 1 ++ echo "$owner/$repo" ++ return 0 ++ fi ++ return 1 ++} ++ ++# Resolve GITHUB_REPOSITORY for {{GITHUB_REPOSITORY}} in renovate.json (after template copy) ++resolve_github_repository() { ++ if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then ++ echo "GitHub repository (Renovate): $GITHUB_REPOSITORY (from environment)" ++ return 0 ++ fi ++ local url="" ++ if [[ -d "$WORKSPACE_DIR/.git" ]]; then ++ url=$(git -C "$WORKSPACE_DIR" remote get-url origin 2>/dev/null || true) ++ fi ++ if [[ -n "$url" ]]; then ++ local parsed="" ++ if parsed=$(parse_github_remote "$url"); then ++ GITHUB_REPOSITORY="$parsed" ++ echo "GitHub repository (Renovate): $GITHUB_REPOSITORY (from git remote origin)" ++ return 0 ++ fi ++ fi ++ if [[ "$NO_PROMPTS" == "true" ]]; then ++ echo "Error: GITHUB_REPOSITORY is required with --no-prompts (e.g. export GITHUB_REPOSITORY=org/repo), or use a workspace with github.com origin." >&2 ++ exit 1 ++ fi ++ read -rp "Enter GitHub repository for Renovate (owner/repo, e.g. vig-os/myapp): " GITHUB_REPOSITORY ++ if [[ -z "$GITHUB_REPOSITORY" ]]; then ++ echo "Error: GitHub repository is required to fill renovate.json." >&2 ++ exit 1 ++ fi ++ echo "GitHub repository (Renovate): $GITHUB_REPOSITORY" ++} +``` + +resolve_github_repository accepts GITHUB_REPOSITORY from the environment or user prompt without validating it matches an owner/repo GitHub slug. Since this value is later substituted into renovate.json, an invalid value (whitespace, extra path segments, shell metacharacters, etc.) can silently produce a broken Renovate config. Consider validating the final GITHUB_REPOSITORY against the same allowlist used in parse_github_remote (^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$) and erroring/re-prompting if invalid. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:59 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3064093642) + + Addressed in 16c5c5c: values from `GITHUB_REPOSITORY` (environment or interactive prompt) must match `^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$` or we exit with a clear error; BATS covers the invalid-env path. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 11:50 AM_ + +_File: [`packages/vig-utils/src/vig_utils/renovate_changelog_pr.py (line 183 RIGHT)`](https://github.com/vig-os/devcontainer/pull/513#discussion_r3064025455)_ + +```diff +@@ -0,0 +1,242 @@ ++"""Parse Renovate PR metadata and insert a Keep-a-Changelog entry (Refs: #506).""" ++ ++from __future__ import annotations ++ ++import argparse ++import os ++import re ++import sys ++from pathlib import Path ++ ++ ++def _strip_md_link(cell: str) -> str: ++ s = cell.strip() ++ m = re.match(r"\[([^\]]+)\]\([^)]+\)", s) ++ if m: ++ return m.group(1).strip() ++ return s ++ ++ ++def _parse_change_cell(cell: str) -> tuple[str | None, str | None]: ++ """Return (old, new) from a Renovate-style change cell.""" ++ text = cell.strip() ++ m = re.search(r"`([^`]+)`\s*->\s*`([^`]+)`", text) ++ if m: ++ return m.group(1).strip(), m.group(2).strip() ++ # Digest / unquoted: abc -> def ++ m = re.search(r"(\S+)\s*->\s*(\S+)", text) ++ if m: ++ return m.group(1).strip(), m.group(2).strip() ++ return None, None ++ ++ ++def _parse_table_updates(body: str) -> list[tuple[str, str | None, str | None]]: ++ rows: list[tuple[str, str | None, str | None]] = [] ++ for raw_line in body.splitlines(): ++ line = raw_line.strip() ++ if not line.startswith("|"): ++ continue ++ parts = [p.strip() for p in line.split("|")] ++ # leading/trailing empty from split ++ cells = [c for c in parts if c != ""] ++ if len(cells) < 2: ++ continue ++ if re.match(r"^-+$", cells[0].replace(" ", "")): ++ continue ++ first = _strip_md_link(cells[0]) ++ if first.lower() in ("package", "name", "dependency"): ++ continue ++ old_v, new_v = None, None ++ for cell in reversed(cells): ++ o, n = _parse_change_cell(cell) ++ if o is not None and n is not None: ++ old_v, new_v = o, n ++ break ++ if new_v is not None: ++ rows.append((first, old_v, new_v)) ++ return rows ++ ++ ++def _parse_title_updates(title: str) -> list[tuple[str, str | None, str | None]]: ++ t = title.strip() ++ # digest: update actions/checkout digest to ++ m = re.search( ++ r"update\s+([^\s]+)\s+digest\s+to\s+(\S+)", ++ t, ++ re.IGNORECASE, ++ ) ++ if m: ++ return [(m.group(1), None, m.group(2))] ++ # update dependency to ++ m = re.search( ++ r"update\s+dependency\s+(\S+)\s+to\s+(\S+)", ++ t, ++ re.IGNORECASE, ++ ) ++ if m: ++ return [(m.group(1), None, m.group(2))] ++ # update to (no "dependency") ++ m = re.search(r"update\s+(\S+)\s+to\s+(\S+)", t, re.IGNORECASE) ++ if m: ++ return [(m.group(1), None, m.group(2))] ++ return [] ++ ++ ++def parse_renovate_pr_updates( ++ title: str, body: str ++) -> list[tuple[str, str | None, str | None]]: ++ from_table = _parse_table_updates(body) ++ if from_table: ++ return from_table ++ return _parse_title_updates(title) ++ ++ ++def format_changelog_entry( ++ pr_number: int, ++ repo_html_url: str, ++ updates: list[tuple[str, str | None, str | None]], ++) -> str: ++ base = repo_html_url.rstrip("/") ++ pr_url = f"{base}/pull/{pr_number}" ++ pr_link = f"([#{pr_number}]({pr_url}))" ++ if len(updates) == 1: ++ pkg, old_v, new_v = updates[0] ++ if old_v: ++ title = f"Renovate: update `{pkg}` from `{old_v}` to `{new_v}`" ++ else: ++ title = f"Renovate: update `{pkg}` to `{new_v}`" ++ return f"- **{title}** {pr_link}\n" ++ lines = [f"- **Renovate dependency update** {pr_link}"] ++ for pkg, old_v, new_v in updates: ++ if old_v: ++ lines.append(f" - Update `{pkg}` from `{old_v}` to `{new_v}`") ++ else: ++ lines.append(f" - Update `{pkg}` to `{new_v}`") ++ return "\n".join(lines) + "\n" ++ ++ ++def _pr_marked_in_changed(unreleased: str, pr_number: int) -> bool: ++ needle = f"[#{pr_number}](" ++ changed_idx = unreleased.find("### Changed") ++ if changed_idx == -1: ++ return False ++ next_hdr = re.search(r"\n### (?!Changed)\w+", unreleased[changed_idx:]) ++ if next_hdr: ++ changed_block = unreleased[changed_idx : changed_idx + next_hdr.start()] ++ else: ++ changed_block = unreleased[changed_idx:] ++ return needle in changed_block ++ ++ ++def insert_renovate_changelog_entry( ++ changelog: str, ++ pr_number: int, ++ entry: str, ++) -> tuple[str, bool]: ++ lines = changelog.splitlines(keepends=True) ++ unreleased_start: int | None = None ++ unreleased_end: int | None = None ++ for i, line in enumerate(lines): ++ if line.startswith("## Unreleased"): ++ unreleased_start = i ++ break ++ if unreleased_start is None: ++ return changelog, False ++ for j in range(unreleased_start + 1, len(lines)): ++ if lines[j].startswith("## [") and "[Unreleased]" not in lines[j]: ++ unreleased_end = j ++ break ++ if unreleased_end is None: ++ unreleased_end = len(lines) ++ block = "".join(lines[unreleased_start:unreleased_end]) ++ if _pr_marked_in_changed(block, pr_number): ++ return changelog, False ++ ++ changed_idx: int | None = None ++ next_sec: int | None = None ++ for i in range(unreleased_start, unreleased_end): ++ line = lines[i] ++ if line.startswith("### Changed"): ++ changed_idx = i ++ continue ++ if ( ++ changed_idx is not None ++ and line.startswith("### ") ++ and not line.startswith("### Changed") ++ ): ++ next_sec = i ++ break ++ if changed_idx is None: ++ return changelog, False ++ ++ # Insert before the next ### heading, or at end of ## Unreleased if Changed is last subsection ++ insert_at = next_sec if next_sec is not None else unreleased_end ++ ++ # Keep blank line between list items and the following heading (Keep-a-Changelog) ++ while insert_at > changed_idx + 1 and lines[insert_at - 1].strip() == "": ++ insert_at -= 1 +``` + +The insertion-point adjustment that moves insert_at backward across blank lines preserves the blank line before the next heading, but it also collapses the blank line between '### Changed' and the first list item when the Changed section is empty (i.e., only blank lines before the next heading). That produces '### Changed' immediately followed by a '-' bullet, which diverges from the Keep-a-Changelog formatting used elsewhere in the file. Consider stopping the backward scan from crossing the first blank line after '### Changed', and instead ensure there’s still a blank line separating the inserted bullet(s) from the following heading. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 11:59 AM — [link](https://github.com/vig-os/devcontainer/pull/513#discussion_r3064093719) + + Addressed in a283fc7: insertion uses a minimum `insert_at` when the line after `### Changed` is blank so the first bullet stays separated from the heading per Keep-a-Changelog; `test_insert_empty_changed_section` asserts `### Changed` + blank + bullet. + + +--- +--- + +## Commits + +### Commit 1: [e63e4b1](https://github.com/vig-os/devcontainer/commit/e63e4b11069ed8841230b3163db538723b6e58f9) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 08:42 AM +chore: replace Dependabot with Renovate configuration, 215 files modified + +### Commit 2: [b7b01e3](https://github.com/vig-os/devcontainer/commit/b7b01e316364d2a470546722ab0f20050b060e80) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 08:42 AM +ci: add .vig-os version file and resolve-image action, 88 files modified (.github/actions/resolve-image/action.yml, .github/workflows/release.yml, .vig-os, scripts/manifest.toml) + +### Commit 3: [1a794ab](https://github.com/vig-os/devcontainer/commit/1a794abcaa91171b1c7af23bc8a1fba22b38816f) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 08:42 AM +refactor(ci): run sync workflows in devcontainer image, 173 files modified (.github/workflows/sync-issues.yml, .github/workflows/sync-main-to-dev.yml) + +### Commit 4: [e1431be](https://github.com/vig-os/devcontainer/commit/e1431beafb764ec67fc57136818fd22c301a02bb) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 08:44 AM +feat: resolve GITHUB_REPOSITORY for Renovate preset extends, 290 files modified (Containerfile, assets/init-workspace.sh, assets/parse-github-remote-lib.sh, install.sh, tests/bats/init-workspace.bats, tests/bats/install.bats, tests/conftest.py) + +### Commit 5: [3efa03d](https://github.com/vig-os/devcontainer/commit/3efa03d8ba9a7191297ef7fa1280144b67e514b3) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 08:44 AM +feat: add renovate-changelog-pr tool and workflow, 609 files modified + +### Commit 6: [4655b57](https://github.com/vig-os/devcontainer/commit/4655b57537dc605c34e5684c44c737390cf39b03) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 08:45 AM +docs: document Renovate migration and changelog automation, 40 files modified (CHANGELOG.md, assets/workspace/.devcontainer/CHANGELOG.md) + +### Commit 7: [f97b2e9](https://github.com/vig-os/devcontainer/commit/f97b2e9c1fd605b897bded44abcf5665f29e78f3) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 08:45 AM +chore: merge origin/dev into chore/509-switch-dependabot-to-renovate, 215 files modified (docs/pull-requests/pr-510.md) + +### Commit 8: [1c82b97](https://github.com/vig-os/devcontainer/commit/1c82b9716cb9dc7b57c014f8a929cfbd73bce955) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:33 AM +test: clear GITHUB_REPOSITORY in BATS so CI env does not skew tests, 6 files modified (tests/bats/init-workspace.bats) + +### Commit 9: [54f4346](https://github.com/vig-os/devcontainer/commit/54f43466c776cd04d4198384cb48eca38865258e) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:39 AM +test: verify parse_github_remote rejects invalid charset, 5 files modified (tests/bats/init-workspace.bats) + +### Commit 10: [eb9bb4a](https://github.com/vig-os/devcontainer/commit/eb9bb4acd7c28b4bbc98c0fcbf5fa626f88b81ff) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:40 AM +fix: validate GitHub owner/repo charset in parse_github_remote, 16 files modified (assets/parse-github-remote-lib.sh) + +### Commit 11: [0f3a4c0](https://github.com/vig-os/devcontainer/commit/0f3a4c01b62faa64c165dda99e868593ae1cc8b9) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:40 AM +fix: sanitize parse_github_remote result in install.sh, 2 files modified (install.sh) + +### Commit 12: [acb4b4a](https://github.com/vig-os/devcontainer/commit/acb4b4a5dcf8f79041e0a54e8e02ff88fb4c555b) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:41 AM +test: verify blank line before heading after changelog insertion, 19 files modified (packages/vig-utils/tests/test_renovate_changelog_pr.py) + +### Commit 13: [294c1d4](https://github.com/vig-os/devcontainer/commit/294c1d45489db51803dd8334142844c1e49715f8) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:41 AM +fix: preserve blank line before heading in changelog inserter, 4 files modified (packages/vig-utils/src/vig_utils/renovate_changelog_pr.py) + +### Commit 14: [a283fc7](https://github.com/vig-os/devcontainer/commit/a283fc74bdd43e80dca8c8d853ebca62ba7660ed) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:57 AM +fix(vigutils): preserve blank line after ### Changed for empty section, 10 files modified (packages/vig-utils/src/vig_utils/renovate_changelog_pr.py, packages/vig-utils/tests/test_renovate_changelog_pr.py) + +### Commit 15: [16c5c5c](https://github.com/vig-os/devcontainer/commit/16c5c5cbe8e849c34f158ee9f94b77d5e2fb119e) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:58 AM +fix(setup): validate GITHUB_REPOSITORY slug in resolve_github_repository, 14 files modified (assets/parse-github-remote-lib.sh, tests/bats/init-workspace.bats) + +### Commit 16: [852d77a](https://github.com/vig-os/devcontainer/commit/852d77aea0b3bfcd80ab6c155b2b07ebdd6144fc) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 11:58 AM +fix(setup): validate parsed GitHub remote owner/repo in install.sh, 16 files modified (install.sh) diff --git a/docs/pull-requests/pr-514.md b/docs/pull-requests/pr-514.md new file mode 100644 index 00000000..73ef1535 --- /dev/null +++ b/docs/pull-requests/pr-514.md @@ -0,0 +1,92 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/512-remediate-latest-image-vulnerabilities → dev +created: 2026-04-10T12:39:26Z +updated: 2026-04-10T12:47:58Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/514 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-04-10T12:47:57Z +synced: 2026-04-10T13:54:31.904Z +--- + +# [PR 514](https://github.com/vig-os/devcontainer/pull/514) fix: remediate nightly Trivy gate vulnerabilities (OpenSSL, gh, typos) + +## Description + +Remediate HIGH/CRITICAL vulnerabilities found by the nightly Trivy security scan on the `:latest` image. Upgrades fixable packages (OpenSSL) and refreshes `.trivyignore` with properly documented suppressions for unfixable base-image CVEs and Go stdlib issues in vendored binaries. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- **`Containerfile`** — Update `python:3.12-slim-bookworm` pinned digest; add targeted `apt-get install --only-upgrade` for `libssl3` and `openssl` to `3.0.19-1~deb12u2` (CVE-2026-28390, CVE-2026-31790). +- **`.trivyignore`** — Drop resolved suppressions (gh/docker-cli CVE-2025-15558, gRPC CVE-2026-33186, older Go stdlib CVEs); add suppressions for unfixable base-image CVEs (ncurses CVE-2025-69720, SQLite CVE-2025-7458, systemd CVE-2026-29111, zlib/minizip CVE-2023-45853), new Go stdlib CVEs in gh (CVE-2026-32281, CVE-2026-32288, CVE-2026-32289), typos dependency (CVE-2026-25727), and `jwt-token` false positive. +- **`CHANGELOG.md`** / **`assets/workspace/.devcontainer/CHANGELOG.md`** — Add Security entry under Unreleased. + +## Changelog Entry + +### Security + +- **Nightly Trivy gate remediation (OpenSSL, gh, typos)** ([#512](https://github.com/vig-os/devcontainer/issues/512)) + - Pin `python:3.12-slim-bookworm` to current digest and add targeted `libssl3`/`openssl` upgrade to `3.0.19-1~deb12u2` (CVE-2026-28390, CVE-2026-31790) + - Refresh `.trivyignore`: drop resolved gh/docker-cli and gRPC entries; add Go stdlib and typos-related suppressions plus `jwt-token` false positive + - Suppress unfixable base-image CVEs: ncurses (CVE-2025-69720), SQLite (CVE-2025-7458), systemd (CVE-2026-29111), zlib/minizip (CVE-2023-45853) + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A — changes are to Containerfile build directives and Trivy suppression metadata. Verification will occur via the CI image build and nightly Trivy scan. + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [x] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +This PR addresses the automated issue opened by the nightly Trivy security scan workflow. The OpenSSL CVEs are fixable via an apt upgrade; remaining findings are unfixable in Debian bookworm stable and are suppressed with risk assessments and expiration dates in `.trivyignore`. + +Refs: #512 + + + +--- +--- + +## Commits + +### Commit 1: [230379a](https://github.com/vig-os/devcontainer/commit/230379a6905aca9905ff8f8607c80590f59f8a4e) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 12:29 PM +fix: remediate nightly Trivy gate vulnerabilities (OpenSSL, gh, typos), 146 files modified (.trivyignore, CHANGELOG.md, Containerfile, assets/workspace/.devcontainer/CHANGELOG.md) diff --git a/docs/pull-requests/pr-515.md b/docs/pull-requests/pr-515.md new file mode 100644 index 00000000..ca566e4f --- /dev/null +++ b/docs/pull-requests/pr-515.md @@ -0,0 +1,173 @@ +--- +type: pull_request +state: closed (merged) +branch: feature/511-smoke-test-dispatch-promote-release → dev +created: 2026-04-10T13:03:41Z +updated: 2026-04-10T13:19:26Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/515 +comments: 4 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-04-10T13:19:25Z +synced: 2026-04-10T13:54:30.933Z +--- + +# [PR 515](https://github.com/vig-os/devcontainer/pull/515) feat(ci): smoke-test dispatch runs promote-release for final tags + +## Description + +Updates the smoke-test `repository-dispatch` workflow so **final** releases dispatch downstream `promote-release.yml` (publish draft release, merge release PR, RC tag cleanup) instead of merging the release PR from the listener alone. **RC** dispatches wait for required checks on the release PR and stop with the PR left open. Preflight now requires `promote-release.yml` on the `dev` ref. Cross-repo gate docs describe the split behavior and that the canonical flow no longer needs a manual publish on smoke-test before upstream promote. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Add `promote-release.yml` to preflight `REQUIRED_WORKFLOWS`. + - Replace `merge-release-pr` with `wait-release-pr-ci` (`gh pr checks --required`, fail/cancel handling, 30m timeout). + - Add `trigger-promote-release` for `release_kind == final` only: dispatch and poll `promote-release.yml` on `dev` with `version` = base semver. + - Update `summary` / `notify-failure` dependencies and result checks (final requires promote success; candidate requires promote skipped). +- `docs/CROSS_REPO_RELEASE_GATE.md` — contract, receiver steps, promote gate wording for automated downstream publish. +- `CHANGELOG.md` and `assets/workspace/.devcontainer/CHANGELOG.md` — Unreleased **Changed** entry for #511. + +## Changelog Entry + +```diff +@@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 + - **Sync workflows run in devcontainer image** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - `sync-issues` and `sync-main-to-dev` use `resolve-image` and run inside the pinned devcontainer, removing the `setup-env` composite action dependency and the inlined retry helper + - `sync-main-to-dev` creates sync branches via `git push` instead of the GitHub refs API ++- **Smoke-test dispatch triggers promote-release for final releases** ([#511](https://github.com/vig-os/devcontainer/issues/511)) ++ - Final releases dispatch downstream `promote-release.yml` instead of merging the release PR directly, publishing the draft GitHub Release and satisfying the upstream promote-time downstream gate ++ - RC releases wait for release PR required checks but no longer merge the PR to `main` + + ### Deprecated +``` + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A — GitHub Actions workflow and documentation only; end-to-end validation happens after the template is deployed to `vig-os/devcontainer-smoke-test` (see issue prerequisite: PR #510 merged there). + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #511 + + + +--- +--- + +## Review Threads (2) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 01:10 PM_ + +_File: [`assets/smoke-test/.github/workflows/repository-dispatch.yml`](https://github.com/vig-os/devcontainer/pull/515#discussion_r3064455110)_ + +```diff +@@ -703,21 +695,106 @@ jobs: + ELAPSED=0 + + while [ "${ELAPSED}" -lt "${TIMEOUT}" ]; do +- STATE="$(gh pr view "${PR_URL}" --json state --jq '.state' 2>/dev/null || echo unknown)" +- if [ "${STATE}" = "MERGED" ]; then +- echo "Release PR merged: ${PR_URL}" +- exit 0 ++ CHECKS_JSON="$(gh pr checks "${PR_NUMBER}" --required --json bucket 2>/dev/null || echo '[]')" ++ TOTAL="$(echo "${CHECKS_JSON}" | jq 'length')" ++ FAIL_COUNT="$(echo "${CHECKS_JSON}" | jq '[.[] | select(.bucket == "fail")] | length')" ++ CANCEL_COUNT="$(echo "${CHECKS_JSON}" | jq '[.[] | select(.bucket == "cancel")] | length')" ++ PENDING_COUNT="$(echo "${CHECKS_JSON}" | jq '[.[] | select(.bucket == "pending" or .bucket == "skipping")] | length')" +``` + +The polling loop swallows `gh pr checks` errors (`2>/dev/null || echo '[]'`). If the command fails (auth/permission/API outage), `CHECKS_JSON` becomes `[]`, `TOTAL` stays 0, and the job will just spin until timeout with no actionable error. Consider capturing stderr and failing fast when `gh pr checks` exits non-zero, and/or explicitly handling `TOTAL == 0` with a clear message (e.g., no required checks configured or query failed). + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 01:13 PM — [link](https://github.com/vig-os/devcontainer/pull/515#discussion_r3064469674) + + Addressed in 1a178ed: removed silent swallowing of `gh pr checks`. On non-zero exit we log stderr/stdout, warn with attempt number, sleep, and retry until the existing 30m timeout. When the call succeeds but returns no rows, we log a warning and retry so we do not spin with empty JSON as if checks were absent. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on April 10, 2026 at 01:10 PM_ + +_File: [`assets/smoke-test/.github/workflows/repository-dispatch.yml (line 721 RIGHT)`](https://github.com/vig-os/devcontainer/pull/515#discussion_r3064455135)_ + +```diff +@@ -703,21 +695,106 @@ jobs: + ELAPSED=0 + + while [ "${ELAPSED}" -lt "${TIMEOUT}" ]; do +- STATE="$(gh pr view "${PR_URL}" --json state --jq '.state' 2>/dev/null || echo unknown)" +- if [ "${STATE}" = "MERGED" ]; then +- echo "Release PR merged: ${PR_URL}" +- exit 0 ++ CHECKS_JSON="$(gh pr checks "${PR_NUMBER}" --required --json bucket 2>/dev/null || echo '[]')" ++ TOTAL="$(echo "${CHECKS_JSON}" | jq 'length')" ++ FAIL_COUNT="$(echo "${CHECKS_JSON}" | jq '[.[] | select(.bucket == "fail")] | length')" +``` + +This job no longer checks the release PR state while waiting. If the PR is closed/merged unexpectedly, the loop may keep polling checks (or hit the same silent-error path) until timeout. Consider adding a periodic `gh pr view` state check and failing fast when state is `CLOSED` (or otherwise not open) so the failure mode is immediately visible. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on April 10, 2026 at 01:13 PM — [link](https://github.com/vig-os/devcontainer/pull/515#discussion_r3064470072) + + Addressed in 1a178ed: each loop iteration now runs `gh pr view ... --json state` first. We exit 1 if the PR is `CLOSED`, and exit 0 with a warning if it is `MERGED` (unexpected here but avoids hanging until timeout). + + +--- +--- + +## Commits + +### Commit 1: [f9e5ace](https://github.com/vig-os/devcontainer/commit/f9e5ace55f888a72b34706b37a511e483ec4b689) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 01:02 PM +feat(ci): smoke-test dispatch runs promote-release for final tags, 166 files modified (CHANGELOG.md, assets/smoke-test/.github/workflows/repository-dispatch.yml, assets/workspace/.devcontainer/CHANGELOG.md, docs/CROSS_REPO_RELEASE_GATE.md) + +### Commit 2: [1df513d](https://github.com/vig-os/devcontainer/commit/1df513ddd7b5ab46d10fec5493f2175a87f3e3c9) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 01:12 PM +test(ci): align just.bats with repository-dispatch workflow, 13 files modified (tests/bats/just.bats) + +### Commit 3: [1a178ed](https://github.com/vig-os/devcontainer/commit/1a178edf7f41adf958d38e3a8a1adea21f96725f) by [c-vigo](https://github.com/c-vigo) on April 10, 2026 at 01:12 PM +fix(ci): harden wait-release-pr-ci polling loop, 23 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) diff --git a/docs/pull-requests/pr-516.md b/docs/pull-requests/pr-516.md new file mode 100644 index 00000000..3998dc64 --- /dev/null +++ b/docs/pull-requests/pr-516.md @@ -0,0 +1,67 @@ +--- +type: pull_request +state: open +branch: release/0.3.3 → main +created: 2026-04-10T13:21:21Z +updated: 2026-04-10T13:54:04Z +author: vig-os-release-app[bot] +author_url: https://github.com/vig-os-release-app[bot] +url: https://github.com/vig-os/devcontainer/pull/516 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +synced: 2026-04-10T13:54:29.745Z +--- + +# [PR 516](https://github.com/vig-os/devcontainer/pull/516) chore: release 0.3.3 + +# [Release 0.3.3](https://github.com/vig-os/devcontainer/releases/tag/0.3.3) - 2026-04-10 + +This PR prepares release 0.3.3 for merge to main. + +## [0.3.3](https://github.com/vig-os/devcontainer/releases/tag/0.3.3) - 2026-04-10 + +### Added + +- **Renovate changelog automation** ([#506](https://github.com/vig-os/devcontainer/issues/506)) + - `renovate-changelog-pr` CLI tool parses Renovate PR metadata and inserts Keep-a-Changelog entries under `## Unreleased` + - `renovate-changelog` workflow runs on `pull_request_target` for `renovate[bot]` PRs in both upstream and workspace template +- **Devcontainer image version pinning** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - `.vig-os` file at repo root declares `DEVCONTAINER_VERSION` as the single source of truth for CI container image tags + - `resolve-image` composite action resolves the image tag and validates it exists in GHCR +- **`GITHUB_REPOSITORY` resolution for workspace init** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - `parse-github-remote-lib.sh` extracts `owner/repo` from HTTPS, SSH, and `git@` GitHub URLs + - `install.sh` gains `--repo` flag; `init-workspace.sh` replaces `{{GITHUB_REPOSITORY}}` in workspace template files + +### Changed + +- **Switch from Dependabot to Renovate** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - Replace `.github/dependabot.yml` with `renovate.json` and shared `renovate-default.json` preset + - Renovate covers all ecosystems previously tracked (github-actions, pip, npm, docker) plus template directories not reachable by Dependabot +- **Sync workflows run in devcontainer image** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - `sync-issues` and `sync-main-to-dev` use `resolve-image` and run inside the pinned devcontainer, removing the `setup-env` composite action dependency and the inlined retry helper + - `sync-main-to-dev` creates sync branches via `git push` instead of the GitHub refs API +- **Smoke-test dispatch triggers promote-release for final releases** ([#511](https://github.com/vig-os/devcontainer/issues/511)) + - Final releases dispatch downstream `promote-release.yml` instead of merging the release PR directly, publishing the draft GitHub Release and satisfying the upstream promote-time downstream gate + - RC releases wait for release PR required checks but no longer merge the PR to `main` + +### Removed + +- **Dependabot configuration** ([#509](https://github.com/vig-os/devcontainer/issues/509)) + - Delete `.github/dependabot.yml` and `assets/workspace/.github/dependabot.yml` + +### Fixed + +- **Promote-release draft release validation** ([#507](https://github.com/vig-os/devcontainer/issues/507)) + - Use the paginated releases list API with jq instead of `GET /releases/tags/{tag}`, which returns 404 for draft releases + - Apply the same release lookup for RC git tag cleanup in upstream and workspace `promote-release.yml` + +### Security + +- **Nightly Trivy gate remediation (OpenSSL, gh, typos)** ([#512](https://github.com/vig-os/devcontainer/issues/512)) + - Pin `python:3.12-slim-bookworm` to current digest and add targeted `libssl3`/`openssl` upgrade to `3.0.19-1~deb12u2` (CVE-2026-28390, CVE-2026-31790) + - Refresh `.trivyignore`: drop resolved gh/docker-cli and gRPC entries; add Go stdlib and typos-related suppressions plus `jwt-token` false positive + - Suppress unfixable base-image CVEs: ncurses (CVE-2025-69720), SQLite (CVE-2025-7458), systemd (CVE-2026-29111), zlib/minizip (CVE-2023-45853) + From 785ef818d75b6989f0ee3a6de657ba1aadf3a390 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Fri, 10 Apr 2026 17:40:54 +0200 Subject: [PATCH 4/4] fix(ci): grant validate job contents:write to see draft releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GitHub API only returns draft releases to tokens with push access. The validate job had contents:read, so the draft release check always failed. Elevate to contents:write and use github.token for the workspace template variant (upstream already used github.token). TDD skipped: CI workflow YAML — only verifiable via GitHub Actions run. Refs: #517 --- .github/workflows/promote-release.yml | 2 +- CHANGELOG.md | 3 +++ assets/workspace/.devcontainer/CHANGELOG.md | 3 +++ assets/workspace/.github/workflows/promote-release.yml | 4 ++-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/promote-release.yml b/.github/workflows/promote-release.yml index 705b8aad..650459af 100644 --- a/.github/workflows/promote-release.yml +++ b/.github/workflows/promote-release.yml @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 15 permissions: - contents: read + contents: write packages: read outputs: version: ${{ steps.vars.outputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e6f759..f514c14a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Promote-release draft release validation** ([#507](https://github.com/vig-os/devcontainer/issues/507)) - Use the paginated releases list API with jq instead of `GET /releases/tags/{tag}`, which returns 404 for draft releases - Apply the same release lookup for RC git tag cleanup in upstream and workspace `promote-release.yml` +- **Promote-release validate job cannot see draft releases** ([#517](https://github.com/vig-os/devcontainer/issues/517)) + - Elevate `validate` job permissions to `contents: write` so the token has push-level access required by the GitHub API to list draft releases + - Use `github.token` instead of the release app token for the draft release check in workspace `promote-release.yml` ### Security diff --git a/assets/workspace/.devcontainer/CHANGELOG.md b/assets/workspace/.devcontainer/CHANGELOG.md index c2e6f759..f514c14a 100644 --- a/assets/workspace/.devcontainer/CHANGELOG.md +++ b/assets/workspace/.devcontainer/CHANGELOG.md @@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Promote-release draft release validation** ([#507](https://github.com/vig-os/devcontainer/issues/507)) - Use the paginated releases list API with jq instead of `GET /releases/tags/{tag}`, which returns 404 for draft releases - Apply the same release lookup for RC git tag cleanup in upstream and workspace `promote-release.yml` +- **Promote-release validate job cannot see draft releases** ([#517](https://github.com/vig-os/devcontainer/issues/517)) + - Elevate `validate` job permissions to `contents: write` so the token has push-level access required by the GitHub API to list draft releases + - Use `github.token` instead of the release app token for the draft release check in workspace `promote-release.yml` ### Security diff --git a/assets/workspace/.github/workflows/promote-release.yml b/assets/workspace/.github/workflows/promote-release.yml index 54eac6d3..766cff43 100644 --- a/assets/workspace/.github/workflows/promote-release.yml +++ b/assets/workspace/.github/workflows/promote-release.yml @@ -53,7 +53,7 @@ jobs: outputs: version: ${{ steps.vars.outputs.version }} permissions: - contents: read + contents: write pull-requests: read defaults: run: @@ -87,7 +87,7 @@ jobs: - name: Verify draft GitHub Release exists env: - GH_TOKEN: ${{ steps.release_app_token.outputs.token }} + GH_TOKEN: ${{ github.token }} VERSION: ${{ steps.vars.outputs.version }} run: | set -euo pipefail