Conversation
Scans merged open-metadata/OpenMetadata PRs daily, filters for doc relevance using the existing upstream-watch-config.md rules, groups related PRs, and opens de-duplicated tracking issues. Draft-PR creation is deliberately out of scope for this first pass.
Commenting /create-draft on a Daily Watcher issue reads the real source PR(s), drafts the doc content with every claim checked against the actual diff, flags anything unverifiable under a Needs-verification heading, and opens a draft PR linked back to the issue. Opt-in per issue, never automatic - mirrors the trust model already used by /content-review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Retires daily-watcher.yml and major-release-completeness-check.yml in favor of two independent, symmetric workflows: - minor-release-watcher.yml: twice-weekly (Mon/Thu), scans only current minor/patch release branches (2.0.0 -> 2.0.1 -> ...), self-healing lookback via real run history instead of a fixed day-count. - major-release-watcher.yml: biweekly (every other Monday), scans only main for the next major (2.0 -> 2.1 -> ...), classifies main PRs as genuinely major-only vs not-yet-backported minor work (backport search first, deep scrutiny of Type of change if none found), and folds in the completeness check for already-tracked multi-PR features. Both add a real dry_run mode (not just a testing hack) and drop the now-redundant needs-docs label per earlier feedback. Verified before commit, via disposable branches (no PR opened): - GITHUB_TOKEN permission chain (label/issue create/comment/edit/close) confirmed working end-to-end against the real API. - Found and fixed: the lookback step's gh run list call had no failure handling -- bash -e would abort the whole step on any API hiccup before ever reaching the documented fallback. Now neutralized. - Found and fixed: dedup only checked each workflow own marker prefix, so neither new workflow would have recognized issues created before the split (e.g. issue 428's daily-watcher marker) -- confirmed live that searching by number alone finds it regardless of prefix. - Found and fixed: dedup never checked for the Backport-title pattern real backport PRs actually use, only Fixes-X -- confirmed against real upstream backport PRs. - Major watcher backport-vs-major classification verified against real PRs both ways: a since-backported PR (glossary pagination, PR 32223 -> PR 32473) and genuinely major-only PRs (dark mode 32383, sourceConfig validation 29566) both classify correctly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Explains the full flow: the problem being solved, design principles, both workflows step by step with their actual prompt logic, the schedule, required secrets/variables, known limitations, and a summary of what was verified before this went anywhere near main. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
main had re-ignored release.config.json and .ai/release-checklist/ via an accidental sweep-in from PR #426 (a homepage-carousel commit with no mention of this tooling) -- confirmed nothing else on main references or uses these paths. Kept our version: both stay tracked, since both new watcher workflows read release.config.json directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sted-content, dry-run, trigger match, stale baseline, unpinned actions - Paginate the upstream gh pr list scans in both watchers (default limit 30 silently dropped most of a busy release window; verified live -- 193 PRs merged to OpenMetadata's 2.0 branch alone since the lookback cutoff). Added --limit 500 to both main scan queries. - Stop advancing the lookback watermark from `gh run list --status success`: a manual dry-run and (for the major watcher) a scheduled off-week both complete successfully without scanning anything or writing a tracker record, so using them as the cutoff permanently skips that interval. Read the persistent digest issue's own latest comment timestamp instead -- it only gets a new comment after a real, non-dry scan actually finishes, so it's an exact, self-maintaining watermark with no separate bookkeeping. Also fixed a real DRY_RUN-vs-"always runs" contradiction in the major watcher's own Part C wording that this fix's correctness depends on. - Committed scripts/check-release-consistency.sh for real and dropped `scripts/` from .gitignore -- both checklists require it, but it was gitignored and never actually in the repo, so a release pass halted immediately. Ran it locally against this repo to confirm it works before committing. - create-draft.yml: stopped handing a write-capable agent an issue's raw, unvalidated body. Anyone can open a public issue; a real MEMBER's /create-draft comment on an attacker-opened one would otherwise feed adversarial content to a session already holding contents:write, pull-requests:write, and push/PR-create tools. Added a deterministic pre-step (no model involved) that requires an exact standalone `/create-draft` command (the job-level check is only a cheap substring pre-filter, and matches on incidental prose like "don't run /create-draft yet"), confirms the issue was actually filed by this repo's own bot identity, and extracts the source PR numbers from the watcher's own marker comments via regex -- gating checkout and the agent step on that validation, and having the prompt act on the pre-extracted PR list rather than free text. Caught and fixed a real bug in this same step during local testing: an empty grep match under `pipefail` was aborting the script before reaching its own graceful error message. - Made dry-run enforcement structural instead of prompt-only in both watchers: `Bash(gh issue create/edit/comment:*)` are now absent from --allowedTools entirely when dry_run is true, so a prompt-injected model can't call them regardless of what it's told -- not just discouraged from doing so. - Added `Bash(gh pr diff:*)` to both watchers' --allowedTools and updated their prompts to actually call it -- they promised real-diff verification but only had `gh pr view` (no patch mode) available. - Updated release.config.json's stale 1.13.4/2.0.0/2026-08-24 baseline to the versions actually shipped since (1.13.5, 2.0.1, 2026-09-02). - Pinned actions/checkout and anthropics/claude-code-action to the same reviewed commit SHAs already used in doc-review.yml/doc-review-auto.yml in this repo, instead of the mutable v6/v1 tags, across all three new workflows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaced two different, both-broken lookback designs with one that's actually been execution-tested: - minor-release-watcher.yml: was using the digest issue's last-comment timestamp (real bug: no author filter, any human reply corrupts the baseline). Now uses gh run list --event schedule --status success -- filtering to schedule events specifically means a manual dry-run workflow_dispatch can never contaminate it, without needing any elevated permissions. - major-release-watcher.yml: same fix, plus one more wrinkle specific to its biweekly gate -- an off-week scheduled run also completes with status success while doing nothing, so --event schedule alone isn't sufficient here. Now walks back through recent schedule runs and checks whether "Ensure tracking labels exist" (already gated to on-week, non-dry runs only) actually ran vs. was skipped, via the Jobs API's step-level conclusion. Both permissions reverted from actions: write back to actions: read -- the repo-variable approach both files had adopted (from whoever fixed these after I last touched them) needs Variables-write access that the default GITHUB_TOKEN does not have regardless of what's declared in permissions, confirmed by direct testing: a live repo variable read/write attempt failed with a 403 "Resource not accessible by integration" on its very first call. That mechanism has never actually worked in either repo. Every piece of this fix was verified with a disposable branch before being written into the real files, not assumed correct from the YAML alone: - Confirmed the repo-variable approach 403s in this environment before ruling it out (not just reasoning about it) - Confirmed gh run list --event <type> actually discriminates by triggering event, with a real push vs. schedule comparison - Confirmed the Jobs API reports step-level success vs skipped correctly, with a real run containing both cases Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both watchers now accept an optional `since` (YYYY-MM-DD) input on workflow_dispatch. When set, it overrides the computed lookback outright and skips the normal run-history walk. Why this is needed: the lookback fix in Fix #1 works by finding the previous successful scheduled run and using its timestamp. The very first real run after merging has no such run to find, so it would fall back to the fixed 4-day (minor) / 15-day (major) window -- silently missing any backlog older than that on day one. `since` lets that first run be pointed at an explicit date instead of guessing. Verified with a disposable push-triggered test branch (test/since-override-check, deleted after use) exercising both branches of the bash logic directly: override set -> its value wins and the step exits before the fallback computation runs; override blank -> fallback computation runs normally. Both passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both watchers now carry a workflow-level env FORCE_DRY_RUN, set to "true". While it's true: - The "Ensure tracking labels exist" step is skipped outright. - DRY_RUN is forced to "true" for the claude-code-action step, regardless of trigger -- including the real schedule cadence, not just a manual workflow_dispatch. - The write commands (`gh issue create`/`edit`/`comment`) are removed from Claude's allowedTools list entirely, not just described as off-limits in the prompt. Even if the model ignored DRY_RUN, it has no tool available to act on that. This makes the first pass after merging safe by construction rather than by trusting a manual toggle: the schedule can fire for real before anyone gets around to testing it, and this guarantees that run can only ever read and report, never write. The plan once merged: run each workflow for real via workflow_dispatch from main (only possible post-merge), read the digest output it prints, and only then flip FORCE_DRY_RUN to "false" in its own separate, reviewable commit. Verified on a disposable push-triggered branch (test/force-dry-run-check, deleted after use) that: - the if: gate keyed on env.FORCE_DRY_RUN actually skips the step - DRY_RUN resolves to "true" from the env context, not just the input - the allowedTools string genuinely omits the write commands All three passed against the exact expression forms used in the real files. (The first attempt at this test had its own bug -- an unquoted colon-space inside a string broke YAML parsing -- caught by the same verification pass meant to catch it.) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces reading release.config.json for version numbers with a deterministic bash step that parses the current version straight out of this repo's own already-published snippets/releases/latest.mdx (the first, and only, <Update label="X.Y.Z Release"> in that file -- it's replaced wholesale on every release, never accumulates). Why: what's "current" is a publishing decision this team already makes and ships -- it shouldn't live in a second, separately-maintained file that can silently drift out of sync with what's actually published. Deriving it from the same page a human already reviews and publishes removes that whole class of drift, and keeps with this system's existing principle of computing anything version/cadence-related in plain bash rather than leaving it to model judgment. Both watchers now compute NEXT_MINOR_VERSION / MINOR_RELEASE_BRANCH / CURRENT_VERSION_DIR (minor) and MINOR_RELEASE_BRANCH / CURRENT_VERSION_DIR / NEXT_MAJOR_VERSION / NEXT_MAJOR_DIR (major) once, in bash, and pass them to the claude-code-action step as env vars -- same pattern as LOOKBACK. The prompts no longer instruct the model to read release.config.json or iterate over its version directories. This also simplifies scope going forward: docs-om previously tracked multiple minor lines at once (e.g. 1.13.x alongside 2.0.x) via release.config.json's versions[] array. The minor watcher now scans only the single current line (MINOR_RELEASE_BRANCH) -- a deliberate scope reduction, not an oversight; older lines are no longer watched for new doc-relevant PRs. release.config.json itself is untouched and still used elsewhere (scripts/check-release-consistency.sh) -- only removed from the watchers' own dependency chain. Verified on two disposable branches: - Confirmed on real docs-collate infrastructure that the same parsing logic correctly fetches this exact file cross-repo (docs-om is public, no token needed) and computes all four values against live data (2.0.1 -> next minor 2.0.2, next major 2.1, branch 2.0) - Confirmed here, locally, that the two directory names this repo specifically needs (current_version_dir, next_major_dir) are not just correctly-formed strings but match real directories that exist on disk (v2.0.x, v2.1.x-SNAPSHOT) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ist out
This branch should carry only what the Minor/Major Release Watchers
themselves need to run. The pre/post-release checklist is a separate,
manually-triggered process with no automation ties to the watchers
(nothing in any workflow calls check-release-consistency.sh), so it
doesn't belong here -- it'll be added on its own branch instead.
Removed from this branch:
- .ai/release-checklist/instructions.md
- .ai/release-checklist/references/{pre,post}-release-checklist.md
- release.config.json (the watchers no longer read it after the
version-derivation fix; its only remaining consumer,
check-release-consistency.sh, is checklist-only)
- scripts/check-release-consistency.sh
Moved (genuinely needed by both watchers, just relocated out of the
checklist-only directory):
- .ai/release-checklist/references/upstream-watch-config.md ->
.ai/release-watchers/upstream-watch-config.md, trimmed to just the
Repo and Doc-relevance-filter sections the watchers actually read --
the Query section was the checklist's own logic (tied to the
release.config.json releaseDate this branch no longer carries), not
something either watcher uses.
.gitignore reverted to main's version -- the promotion of
release.config.json/scripts//.ai/release-checklist/ from personal to
shared tooling belongs with the checklist branch, not this one.
Also brought .ai/release-watchers/PROCESS-OVERVIEW.md fully up to date:
it still described the pre-fix lookback mechanism and the
release.config.json-based version reads. Rewrote the design-principles,
both workflow tables, and the "verified before merge" section to match
current reality, including an explicit, honest split between what's
been proven on disposable branches and what genuinely can't be tested
before this merges (schedule/workflow_dispatch/issue_comment only fire
from the default branch).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
…8, not pending) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…orced-dry lookback, dry-run Write leak
Addresses the latest review round:
1. Every claude-code-action step passed GH_TOKEN only as an env var, never
as the action's own `github_token` input. Confirmed against the pinned
action's actual source (src/github/token.ts): setupGitHubToken() only
reads process.env.OVERRIDE_GITHUB_TOKEN, which action.yml wires
exclusively from the `github_token` input -- an env var alone never
reaches it. Without that input, the action requests an OIDC token
instead, which none of these jobs are permitted to do (no
`id-token: write`), so every Claude step across all three workflows
would have failed outright, every time. Added
`github_token: ${{ github.token }}` to all three invocations.
2. create-draft.yml's single session both read the untrusted upstream
PR's title/body/diff and held push/PR-create/comment credentials.
Prompt instructions telling it to treat that content as data, not
commands, are not an isolation boundary. Split into two sessions: a
read-only one that reads the issue and source PR(s) and writes the doc
content plus a title/body handoff to /tmp/create-draft-handoff/ (no
git/PR/comment tools at all), and a second one that only reads those
handoff files and does the git/push/PR-create/comment work -- it has
no tool that can read the original source PR, so untrusted upstream
content has no path into the write-capable session regardless of what
it says.
3. minor-release-watcher.yml's lookback took the most recent successful
*scheduled* run at face value, but a FORCE_DRY_RUN'd scheduled run also
completes with status success while writing nothing (the write tools
and label step are gated off, not the run itself). Once FORCE_DRY_RUN
is switched off, that would have made the first real run's baseline
the most recent preview, silently discarding everything found-but-
never-tracked during the whole dry-run rollout period. Ported the same
fix the major watcher already had for its own off-week no-op runs:
walk backward through recent scheduled runs and use the first one
where "Ensure tracking labels exist" (gated off during both
FORCE_DRY_RUN and a manual dry-run) actually ran, not just completed.
The major watcher's own lookback already avoided this incidentally
(its existing off-week check happens to also exclude FORCE_DRY_RUN'd
runs), so it needed no change.
4. FORCE_DRY_RUN removed the `gh issue create/edit/comment` tools from
the model's allowedTools, but left `Write` unconditionally available,
and the Slack-send step had no dry-run gate at all in either watcher.
The prompt says not to write slack-digest.txt in dry-run mode, but
that was only ever a prompt instruction -- the tool to do it anyway
was never actually removed, so an AI session that wrote the file
regardless (confusion, or injected instructions from a hostile PR
title/body) would have resulted in a real external Slack post despite
the "safe by construction" design this branch's whole first-live-
rollout plan depends on. Moved `Write` inside the same dry-run-gated
branch as the issue-write commands, and added the missing dry-run gate
to the Slack-send step in both watchers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressed all 4 P1 findings in b8d03b7:
|
…both release watchers (#461) A single Claude session in both minor-release-watcher.yml and major-release-watcher.yml read untrusted upstream open-metadata/OpenMetadata PR titles, bodies, and diffs while also holding gh issue create/edit/comment tools for this repo. A crafted upstream PR could steer those issue mutations regardless of any prompt instructions to treat that content as data, not commands. Splits each workflow into a read-only analysis session (scans, filters, verifies, groups PRs; for the major watcher, also re-checks completeness of already-tracked multi-PR features) that has no issue-mutation tools at all and can only write a structured summary.json handoff to /tmp, and a separate write-capable session that never reads any upstream PR content and only acts on that handoff -- de-duplicating, creating/updating issues, posting the digest, and preparing the Slack summary. Dry-run mode now removes the create/edit/comment tools from the write session's allowedTools entirely rather than just instructing it not to use them, matching the same pattern already applied to create-draft.yml. Mirrors the equivalent split already implemented in docs-collate's parallel release-watcher PR (open-metadata/docs-collate#619). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ayush-shah
left a comment
There was a problem hiding this comment.
Changes requested for the new rollout-backlog P1 below and the residual shared-workspace boundary documented in the existing watcher thread. The older forced-dry watermark finding was not duplicated.
| else | ||
| # First run ever (or every past attempt failed) -- a safe | ||
| # generous default rather than guessing. | ||
| echo "lookback=$(date -u -d '4 days ago' +%Y-%m-%d)" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
[P1] Fail closed when there is no successful live baseline
Initial rollout performs only forced-dry previews, and an explicit since value used for a preview is not persisted. After FORCE_DRY_RUN is disabled, this 20-run lookup can find no live marker and silently falls back to four days (15 in the major watcher), permanently omitting older untracked changes. Require an explicit since value for the first live dispatch and whenever no real baseline is found, or scan from a deterministic exhaustive baseline and persist the successful live watermark. Do not let the 20-run cap age a valid baseline out.
…ted jobs (#461) - Cadence lookup now fails a live run outright when no successful non-dry baseline exists and no explicit `since` was given, instead of silently defaulting to a short fixed window that could permanently miss backlog. Also raised the run-list cap from 20 to 100 so a valid baseline can't age out of reach. - Split each watcher's single job into prepare/scan/notify/slack jobs, each on its own runner and checkout. The scan job's token no longer holds `issues: write` (minor) / holds `issues: read` only (major), enforced by GitHub's job-level permissions rather than relying on allowedTools alone. The handoff between scan and notify now moves via upload-artifact/download-artifact and is deterministically schema- validated -- including that every target_version matches this run's own computed version, and (major) that every completeness_checks issue number is a real, open, major-watcher-marked issue -- before the write-capable session ever reads it.
|
Addressed both findings from the latest review round in bc37420:
|
…reason loss Forced-dry-run previews now write to dry-run-preview.md, published to the job summary, since no digest issue exists to read in that mode (also fixes major-release-watcher's Digest step, which previously wasn't included in the dry-run print-instead list even though its issue-comment tool is unavailable in that mode). The scan job's handoff file now carries tracking_issue/original_pr references and per-item held/ruled_out/needs_a_look reasons, which notify consumes directly instead of re-deriving them from evidence prose or omitting them from the digest entirely. Refs #643
Replaces #457, closed automatically when its source branch was renamed via the GitHub branch-rename API (which deletes the old ref rather than migrating the PR, contrary to what I expected). No commits were lost -- this PR is the exact same branch content under its new name,
docs/release-watchers.Summary
minor-release-watcher.yml— twice weekly (Mon/Thu), scans only the current minor/patch line (2.0.0 -> 2.0.1 -> ...). Never touchesmain.major-release-watcher.yml— biweekly (every other Monday), scans onlymainfor the next major (2.0 -> 2.1 -> ...). Classifiesmain-branch PRs as genuinely major-only vs. not-yet-backported minor work (backport search first, then deep scrutiny of "Type of change" if none found), and folds in a completeness check for already-tracked multi-PR features.Both add a real
dry_runmode (a genuine preview option, not just a testing artifact), asinceoverride for the first catch-up run (see Testing below), and drop the redundantneeds-docslabel — every issue either workflow files already means that by existing.Also includes
.ai/release-watchers/PROCESS-OVERVIEW.md— full process documentation: overview, design principles, both workflows step by step with their actual prompt logic, the schedule, required secrets/variables, and known limitations. (This doc still describes the pre-fix lookback mechanism in a couple of places -- a correction pass is planned once everything below settles.)Testing — what's actually been verified, and how
Everything below was checked on disposable branches, never a PR, per explicit request. Being specific here on purpose, since an earlier version of this description overstated what had been confirmed.
Confirmed working, via real live-API calls on disposable branches:
ghCLI commands this workflow depends on (label create, issue create/comment/edit/close) work against the live API with the defaultGITHUB_TOKENGITHUB_TOKENgets a 403 on that API regardless of declared permissions, so it never actually worked. Replaced withgh run list --event schedule, which only needs read access and was confirmed to filter correctly by trigger eventstatus: successwhile skipping its real work -- confirmed via the Jobs API that per-stepskippedvs.successis reported correctly, and the lookback walk uses that to find the last real on-week run, not just the last non-erroring onesinceoverride input: confirmed it takes priority over the computed lookback when set, and falls through to the normal computation when blankFORCE_DRY_RUNsafety switch added for the first live rollout: confirmed theif:gate keyed on it actually skips the label-creation step,DRY_RUNresolves totruefrom it, and Claude's allowed tools genuinely omitgh issue create/edit/commentwhile it's on -- not just told not to use them, physically unable todaily-watcher:pr-Xfrom before this workflow existed, and the new per-workflow prefix), confirmed live that searching by number alone finds eitherNOT yet verified -- and can't be, before this merges:
schedule,workflow_dispatch, andissue_commenttriggers only fire from a repo's default branch -- this is a GitHub platform constraint, not something we chose to skip. That means the actualclaude-code-actionstep (the part that reads real PRs, applies the filter/verify logic, and would create/update issues) has never executed successfully in a real run yet -- only the surrounding bash/ghlogic above has been proven live. An earlier attempt to test it via apush-triggered disposable branch failed outright (claude-code-actiondoesn't supportpushas a trigger event) -- that failure was found only while re-verifying this PR's own claims, and is why this section is now this explicit.Post-merge steps (do these before considering this fully live)
minor-release-watcher.ymlandmajor-release-watcher.ymlviaworkflow_dispatchfrommain.FORCE_DRY_RUNis hardcoded to"true"in both files, so this is safe regardless of what's passed in -- neither workflow can create, edit, or comment on anything yet.FORCE_DRY_RUNto"false"in its own small, separate, reviewable commit -- that's the actual go-live moment.create-draft.yml's real git-push/PR-open path via an actual/create-draftcomment -- also blocked pre-merge for the same platform reason above.docs-collateis explicitly out of scope for this PR. It currently has its own, separate copy of the same broken lookback mechanism found and fixed here; that gets ported over in a dedicated follow-up pass after this merges.