Find the stories git can't name, and reconcile the Deploy Ready backlog - #3704
Open
yodem wants to merge 6 commits into
Open
Find the stories git can't name, and reconcile the Deploy Ready backlog#3704yodem wants to merge 6 commits into
yodem wants to merge 6 commits into
Conversation
…y Ready backlog
The prod release-notes pipeline resolved shipped stories in one direction
only -- git to Shortcut -- and only ever within the current prod tag range.
Two consequences, both observed live against the 20 stories standing in
Deploy Ready after the 7.1.3 rollout:
- A PR whose branch and commit subject carry no story code was invisible,
even though Shortcut itself held the PR-to-story link. (1 of 20.)
- Anything that shipped in an earlier range was never revisited and stayed
in Deploy Ready permanently. (10 of 20 -- the dominant cause.)
shipped_stories.py gains Shortcut's PR link as a third discovery source,
resolved via `search/stories?query=pr:<N>`, feeding both the transition and
the announcement prose.
reconcile_deploy_ready.py (new) sweeps every non-archived Deploy Ready story
org-wide each release and transitions those whose linked PR already reached
prod, whichever release shipped it. Enumeration uses the token'd search
endpoint, which -- unlike iterations-get-active -- is not scoped to the
caller's own teams. Shipping evidence is a PR merge commit being an ancestor
of the prod tag, not a commit-subject grep. It is dry-run by default and
never writes into shipped-stories.json: those stories shipped in earlier
releases, and leaking them into today's announcement would have Slack claim
old features shipped today. Its report goes to $RUNNER_TEMP rather than the
checkout, because the prose agent holds Glob+Read over its working directory
and a distinct filename is a naming convention, not an access boundary.
Three guards decide whether a linked PR counts as evidence -- merged, the
right repository, and targeting master. Each caught a real false positive:
a story linked to a PR in another repo resolves against this one as an
unrelated older PR, and a promotion merge resolves to a story just as
readily as that story's real feature PR does. They live in one shared module
because the first implementation applied them to the sweep but not to the
new fallback, and the two drifted immediately.
Stories with no resolvable PR are reported for human triage rather than
guessed at.
Tests are not collected by pytest.ini and must be run by path with
`-p no:django -c /dev/null`; see the README.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJqBksQqzHYs3F54Y4tJRX
mark_stories_deployed.py and reconcile_deploy_ready.py only ever wrote a story's workflow state; nothing recorded which release actually carried it. Both now post a short comment via the shared shortcut_comment.py immediately after a real transition. mark_stories_deployed.py names its own release directly (it's reading that release's shipped-stories.json); reconcile_deploy_ready.py instead derives the TRUE shipping release from `git tag --list --contains <merge-oid>` and degrades honestly if that can't be resolved, since naming the current prod tag for a backfilled story would be the same "old features shipped today" mistake this pipeline's other separations already guard against. Comments are never posted in dry-run/no-apply mode or with the new --no-comment flag, and a comment failure is reported but never fails the run or rolls back the transition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJqBksQqzHYs3F54Y4tJRX
📊 Code Quality Score: 27/100
Was this score accurate? 👍 Yes · 👎 No Scored by GitVelocity · How are scores calculated? |
…g (sc-47043) reconcile_deploy_ready.py's triage bucket only reports raw diagnostics (no_qualifying_pr / non_standard_workflow_or_state), leaving a human to open every story and work out why individually. This adds an opt-in headless-Claude explainer step that proposes a labeled hypothesis and a suggested next action per triage story. Kept fully out of the deterministic Python: reconcile_deploy_ready.py gained no API client, just richer in-memory triage context (description, comments, per-linked-PR guard diagnostics -- all already available on the same Shortcut search response, no extra calls). A new sibling script, build/ci/triage_explainer.py, is a stdlib-only JSON transform that extracts ONLY the triage bucket into its own file -- shipped/pending are structurally absent, not merely excluded by prompt instruction, since a triage story's description/comments are contributor-controlled text. The actual `claude -p` call lives entirely in a new, opt-in workflow step, provisioned identically to the existing release-notes prose step (--allowedTools Read,Write,Glob,Grep only, no Bash, no network), and its output stays in $RUNNER_TEMP so the prose step can't pick it up. It never mutates Shortcut and never influences the shipped/pending buckets or any transition. Opt-in and off by default on every trigger path: a workflow_dispatch run via its own explain_triage input, the automatic repository_dispatch trigger via a repo-level Actions variable (vars.ENABLE_TRIAGE_EXPLAINER). The decision itself (triage_explainer.resolve_enabled) is tested Python, not inline bash. Degrades cleanly (continue-on-error, same posture as the existing bookkeeping steps) when disabled, when ANTHROPIC_API_KEY is absent, or when the call fails. Note: verified against this repo's actual configured secrets (gh secret list) that the correctly-spelled ANTHROPIC_API_KEY is the only secret that exists -- used that instead of a claimed ANTHOPIC_API_KEY misspelling, which does not exist here and would have silently disabled this feature permanently in production. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJqBksQqzHYs3F54Y4tJRX
… current-release stories into notes Three changes to the Deploy Ready reconciliation pipeline: 1. Remove the Shortcut write-back comment feature added previously (product decision: transitioning to Done is enough, no per-story comment needed). Deletes shortcut_comment.py, the comment-text builders, --no-comment flags, and comment_posted/comment_failed/ would_comment fields from both mark_stories_deployed.py and reconcile_deploy_ready.py. resolve_shipping_release_tag() is kept -- change 3 below needs it. 2. Fix a guard drift in the opposite direction from the one fixed previously: reconcile_deploy_ready.py had no guard against a PR whose HEAD branch is a long-lived environment branch (a promotion PR merging preprod -> master passes merged/repo/target-branch cleanly, since its target really is master). shipped_stories.py's RC1 fallback already had this check for its own purposes; the two scripts drifted apart on whether it existed at all. Unified as a fourth PR-level guard in the shared shortcut_pr_guards.py, applied by both callers. 3. A story the reconciliation sweep backfills can, in the ordinary case, have shipped in the CURRENT release rather than an earlier one -- silently excluding it from today's announcement was a different flavor of the same "say what actually shipped today" mistake, just by omission instead of leakage. reconcile_deploy_ready.py now attaches shipping_release_tag (derived via resolve_shipping_release_tag) and, only when it equals the current prod tag, a hydrated_story sub-object to each shipped report entry. A new, separate, deterministic script (merge_release_backfill.py) trusts that upstream decision and folds only those pre-filtered entries into shipped-stories.json, deduped by id, before the prose step runs -- the reconcile report's shipped/ pending/triage buckets as a whole still never reach that step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJqBksQqzHYs3F54Y4tJRX
Trims module docstrings, function docstrings, and inline comments across the release-notes pipeline scripts, the workflow YAML, and the README down to what's needed operationally. No behavior change: only comments, docstrings, and markdown were touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJqBksQqzHYs3F54Y4tJRX
1. Pass --prod-tag (from shipped-stories.json's range.current_tag) to reconcile_deploy_ready.py in the workflow -- it was silently falling back to the newest prod/* tag on every run, including a workflow_dispatch re-run for an older release, which would wrongly gate hydrated_story against the wrong tag. 2. reconcile_deploy_ready.py now warns and exits non-zero (under --apply only) when it saw stories, transitioned none, and routed some to triage -- mirrors mark_stories_deployed.py's existing silent-no-op guard. 3. Scope the triage-explainer Claude step's cwd to $RUNNER_TEMP -- its Write grant previously covered the checkout, including shipped-stories.json, while processing contributor-controlled text. 4. Gate the RC1 PR-link fallback on `not c["is_revert"]` -- a revert's own merge-commit PR could otherwise get adopted into stories_from_shortcut_pr_link despite never reaching story_ids. 5. Add steps.merge_backfill.outcome == 'failure' to both bookkeeping warn/alert step conditions -- a merge failure was invisible. 6. Pin the Claude Code CLI install to @2.1.263 (the version currently resolving) instead of floating on latest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJqBksQqzHYs3F54Y4tJRX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two prod runs of the release-announcement pipeline left stories stuck in Deploy Ready and under-reported the Slack notes. Root-caused to two independent defects, neither of which is a bug in any single step.
Measured against the 20 stories standing in Deploy Ready after
7.1.3:1. Discovery ran in one direction only
shipped_stories.pyfound a story only ifsc-NNNNNappeared in a commit subject or PR head branch. Shortcut's own PR↔story links were never consulted.This PR's own story is the demonstration: it shipped on branch
feature/prod-rollout-slack-release-notes, so the release-announcement pipeline could not announce itself — even though Shortcut held the link the whole time. 1 of 20.Fixed by adding
search/stories?query=pr:<N>as a second, independent discovery direction.2. Nothing ever revisited work outside the current tag range — the dominant defect
Each run walks
prev_tag..cur_tagonly. A story missed once, or shipped before this pipeline existed, is never looked at again.sc-46548shipped in6.111.0on Aug 12 with a correctly-named branch and was still Deploy Ready a month later. 10 of 20.Fixed by
build/ci/reconcile_deploy_ready.py: an org-wide sweep, every release, over every non-archived Deploy Ready story.Everything here is deterministic
No LLM decides what shipped or writes any state. Story resolution is a git walk plus REST calls; "did it reach prod" is
git merge-base --is-ancestor; transitions are a for-loop. The agent's role is unchanged — it writes prose and holdsRead,Write,Glob,Greponly.Three guards before a linked PR counts as evidence
Each caught a real false positive in live testing:
merged— an unmerged PR shipped nothing.repository_id— a story linking PR#224in another repo otherwise resolves that number against this repo, matches an unrelated older PR, and tests as "in prod".target_branch_name == master— promotion merges resolve to a real story throughpr:<N>just as readily as the genuine feature PR does.They live in one shared module (
shortcut_pr_guards.py). The first pass applied them to the sweep but not to the new fallback and the two drifted immediately — the fallback started adopting story ids from promotion merges.Reconciliation output is kept out of the announcement
Backfilled stories shipped in earlier releases; if they reach the prose step the announcement claims a dozen old features shipped today. The report is written to
$RUNNER_TEMP, not the checkout — the prose agent holdsGlob+Readover its working directory, so a distinct filename is a naming convention, not an access boundary.Same principle in the write-back comment: the sweep derives each story's true release via
git tag --list 'prod/*' --contains <oid>rather than naming the current one. Verified across 9 distinct releases from6.79.0to7.1.3.Reviewer notes
prod-rollout-succeededafter merge transitions the whole standing backlog at once and posts a comment on each — not just that release's stories. Say the word if you'd rather gate it behind one manual run.--applyis required to mutate.--no-commentopts out of the write-back.search/storiesendpoint, which is org-wide — unlikeiterations-get-active, which is silently scoped to the caller's own teams and has produced silently-incomplete results here before.pytest.ini. Run by path:python3 -m pytest build/ci/tests/ -q -p no:django -c /dev/null→ 174 passedBackground and the reusable pattern:
concepts/Shortcut Story-to-Deploy Attributionin the wiki.🤖 Generated with Claude Code
https://claude.ai/code/session_01KJqBksQqzHYs3F54Y4tJRX