Docs release issue ([Docs release] X.Y.Z in elastic/dev) is the source of truth -- not the eng [Release] issue. Create missing docs issues (§1.1) or collect dates from the user. Missing issue URLs: (issue TBD).
Playbooks: v9 -- v8. Templates: minor -- 9.x patch -- 8.x patch.
| Line | Repo | Config files | Deploy watch |
|---|---|---|---|
| 9.x+ | elastic/docs-builder |
config/assembler.yml, config/versions.yml |
docs-internal-workflows bump PRs + prod version-bump (§0.3) |
| 8.x / 7.x | elastic/docs |
shared/versions/stack/*.asciidoc, conf.yaml |
None -- do not use docs-internal-workflows |
Invariants
- IDs, dates, PR URLs, @mentions: from
gh, the docs issue, Slack, or the user. Never invent. Missing →(issue TBD)or[@handle]and ask. - Never mix 8.x and 9.x in one PR. 8.x/7.x:
elastic/docsonly; nodocs-internal-workflows. - Checklist is the step index. Live-fetch only open work. RN merge is not a checkbox -- always
gh pr viewblocking RN URLs. - A PR URL in the RN table is blocking until merged. Skip only
No changes/N/A. Do not exempt Fleet, Agent, or any product by name. - Check boxes this skill just completed. Do not check anyone else's work (including docs-eng). Do not wait to be asked.
- Slack: read slack-templates.md before drafting or sending. Confirm before send; thread follow-ups on the FF announcement.
- Live/released Slack only after the gate (§0.3) and a user eyeball of the site. Do not draft before the gate.
- Empty args / "how's my release": session state → active docs issue. Do not ask which version if one is in flight. After closed-issue cleanup leaves none, stop -- do not rediscover.
- Cleanup = docs issue
CLOSEDonly. 404 = drop that ID and rediscover. No TTL. - RC varies each release -- read from the
#mission-controlcoordination thread or eng issue. Never hardcode a name. - Do not offer a Slack ping or PR whose issue box is already checked, or that the user already said they sent.
Inputs
$ARGUMENTS is a space-separated list of semver versions and/or elastic/dev issue numbers (e.g. 9.4.0 9.3.4 8.19.15 or #1234 #1235). If empty: load session state (§0.1). If it has an active release (anticipated date is today, in the last 2 days, or tomorrow, and the docs issue is not CLOSED), use that. Otherwise ask.
"How's my release" / "how's my release doing" (no versions given): empty-args status. Load session state and run §0. Do not ask which version if an active release is in state. If cleanup left no versions, say the tracked issues are closed and stop.
Match the user's request to the relevant section (status, PRs, reminders) -- they may skip the full pipeline.
0. Check status (run on every invocation)
When versions or issue numbers are known (from $ARGUMENTS or session state), check state before proposing actions.
0.1 Session state (fast path)
Path: ~/.elastic-docs/stack-release-state.json. Local coordinator cache -- never commit it.
On every invocation:
- Read the file. Fall back to scratch if missing, unreadable, invalid JSON, no matching version, or the user says "start fresh".
- Cached IDs only skip discovery:
docsIssue,engIssue,coordinatorPrs.{repo:{number,sha,url}},rnPrs[].{product,url},slack.{docsChannelId,docsFfThreadTs,missionControlChannelId,coordinationThreadTs,beginPublishingTs,handles},deploys(9.x only:configSha,stagingBumpPr,prodBumpPr,stagingRunId,prodRunId). Alsoline,type,anticipatedDate. - Live-refresh only what the docs issue still has open (§0.2). Do not re-
gh pr view/gh run viewchecked lines. Always live-refresh RN PR merge state (the table stores URLs, not merge). Never trust cached JSON for merge/deploy conclusions; do trust a checked issue line that already cites a PR or run URL. - Cached ID 404 / missing message: drop that field and rediscover just that piece. Empty
slack_read_threadon a Buildkite/bot parent (begin publishing,release build declared) is not a 404. - End of §0: drop CLOSED docs issues, then write or delete the file.
deploys is 9.x only. Omit it for 8.x / 7.x.
Closed-issue cleanup: Finished only if state is CLOSED. Report status this turn, then drop that version from releases and active. announced while OPEN is not enough. A 404 is not finished -- drop docsIssue and rediscover (step 4).
- Some versions still open: write remaining entries +
updatedAtnow. Mention which version was dropped. - None remain: delete the file. Do not write
{ "active": [], "releases": {} }. Mention that local state was cleared.
0.2 Stage from the docs issue (do this before extra fetches)
After session state, the first GitHub call is gh issue view. Parse checkboxes immediately. Jump to that stage. Do not walk §1–§5. Do not re-verify checked lines.
gh issue view <N> -R elastic/dev --json body,state
| If the issue shows | Stage | Live-fetch only |
|---|---|---|
| Docs issue CLOSED | done / cleanup | None. Report closed, then closed-issue cleanup (§0.1). |
| Day-before ping or coordinator-PR box unchecked | pre-release | Coordinator PR search (if no cached number) + RN table URLs. Skip #mission-control and §0.3. |
| Coordinator PR checked, merge-RN ping unchecked | waiting to tell writers | RN PR states + (release day only) cached #mission-control threads. |
Config-merge / prod-bump (9.x) / elastic/docs PR (8.x) unchecked |
waiting on publish infra | That PR/deploy (§0.3 for 9.x) + RN PR states + cached #mission-control threads. |
Prod-bump line checked with a run URL (9.x), or elastic/docs merge checked (8.x) |
waiting on RNs / announce | RN PR merge states only. Skip coordinator gh pr view, bump PRs, gh run view. |
Website-confirm and #mission-control live boxes checked |
announced | Report done. No extra fetches unless the user asks. |
RN merge is never a checkbox. Always gh pr view each blocking RN URL.
If a checked line's cited URL 404s, rediscover that line only.
Stakeholder resolution (§ below): run only when a row still has a placeholder, a "specify who's responsible" footnote, or 2+ named people. Skip the #docs FF-thread search when every row already has a single named stakeholder.
GitHub state
If session state has the docs issue number, gh issue view it directly. Otherwise search [Docs release] <version>. Do not gh pr list by version if the issue or session state already has the coordinator PR URL.
Search coordinator PRs only when §0.2 still needs them, and only in the repo for that line (9.x → docs-builder, 8.x → docs). Never gh pr list elastic/docs for a 9.x-only batch.
gh pr list -R elastic/docs-builder --search "<version>" --json number,title,state,url
gh pr list -R elastic/docs --search "<version>" --json number,title,state,url
- Issue checkboxes: If an item is unchecked but this skill already completed it (a Slack message you sent, a PR you opened), check it now using §4. Do not ask. Do not check items for work this skill did not do.
- RN PR status: One shell loop over every PR URL in the Release Notes table:
gh pr view <url> --json state,mergedAt,title
Skip vs blocking: Skip only No changes / N/A. A PR URL is blocking until merged -- do not exempt Fleet, Agent, or any other product by name. Named stakeholder is the owner; do not add ? on a named row.
Report: X/Y merged (skipping N/A rows), Z outstanding. When all blocking RN PRs are merged: "All RN PRs merged."
Then, by line -- only if §0.2 says publish infra is still open:
- 9.x: After the
docs-buildercoordinator PR is merged and the prod-bump issue line is still unchecked, continue to §0.3. If that line is already checked with a run URL, skip §0.3. - 8.x / 7.x: Skip §0.3. Config lives in
elastic/docs. Nodocs-internal-workflowsbump or deploy.
Resolve stakeholder assignments
Run only when a row still needs it (§0.2). Cases: backtick placeholder (e.g. `Beats point person`) or "specify who's responsible" footnote; 2+ named people and one confirmed in thread; named stakeholder out and someone else confirmed covering.
- FF announcement thread (
slack_search_publicfor the version in#docs, thenslack_read_thread). - Scan replies: "I can handle [product]", "I'll do [product]", "I'm drafting the X.Y.Z [product] release notes", or explicit coverage.
- Reverse-map Slack display name → GitHub handle via the issue appendix lookup table. If missing,
slack_search_usersand ask the user for the GitHub handle. - Update the issue body (§4): replace placeholder, narrow to the confirmed person, or swap in the covering person.
- Report: "Updated [Product] stakeholder: [old] → @new-handle (confirmed in thread)."
No confirmation: leave as-is and flag. Do not guess. Ambiguous ("I can help" with no product): ask.
#mission-control (release day only)
If session state has thread timestamps, slack_read_thread those first.
Do not slack_read_channel the whole of #mission-control. Do not slack_search_public for "begin publishing" without a version and after: today's date.
Empty thread on beginPublishingTs / releaseBuildDeclaredTs is expected for Buildkite bot posts. Treat the parent as the signal; do not rediscover.
Only search when a needed cached ts is missing (not merely an empty thread): slack_search_public query like 9.5.2 in:#mission-control after:YYYY-MM-DD, limit 5.
Signals matching tracked versions (link each thread in the status report):
- Coordination thread: "Coordination for ... release, scheduled on" -- timing, who's RC (never assume), schedule changes in replies.
- "release build declared": "We are officially declaring X.Y.Z as the release build".
- "begin publishing": "All pre-finalize steps for X.Y.Z are complete. Please begin the publishing process." -- docs action trigger. Read for go/hold from whoever is RC (step 1).
0.3 Watch docs-internal-workflows (9.x only)
Skip this section for 8.x / 7.x. Do not search that repo, do not poll bump PRs or version-bump runs, and do not require a prod version-bump for 8.x live messages.
Skip §0.3 when the docs issue already checks the "Watch for the prod version-bump deploy" line (or equivalent) and cites a successful prod run URL. Do not re-list bump PRs or re-view runs.
For 9.x, after the docs-builder coordinator PR merges and that issue line is still unchecked:
The coordinator merges docs-builder config PRs and watches the prod version-bump. Do not wait on docs engineering. Merging the coordinator PR (config SHA = merge commit short SHA) automatically opens:
[bump] [staging] docs-builder configuration: <sha>[bump] [prod] docs-builder configuration: <sha>
Those merges kick off Staging / Docs / Deploy / version-bump / ... and Prod / Docs / Deploy / version-bump / ....
Care about two things only:
- Bump PRs merged -- staging and prod
[bump] ... <sha>areMERGED. - Prod version-bump deploy succeeded for that SHA. A failed staging version-bump blocks -- report immediately; do not treat prod as done.
Ignore unrelated workflows (edge, AI enrich, zizmor, dependabot, assembler ingest, link-index) unless the user asks. Do not wait on staging success once prod has succeeded, as long as staging did not fail.
gh pr list -R elastic/docs-internal-workflows --state all \
--search "[bump] docs-builder configuration: <sha>" \
--json number,title,state,mergedAt,url
gh run list -R elastic/docs-internal-workflows --limit 20 \
--json name,conclusion,status,displayTitle,url,databaseId,createdAt
Match runs by version-bump in the name plus timing after the bump PR merge. Cache bump PR numbers and run IDs.
When prod version-bump is completed/success and staging did not fail: "Prod deploy succeeded for ."
Gate -- do not draft "docs are live" / "docs released" until all required:
| Line | Gate |
|---|---|
| 9.x | All blocking RN PRs merged and prod version-bump succeeded (staging did not fail) |
| 8.x / 7.x | All blocking RN PRs merged and the elastic/docs coordinator PR is merged |
Until the gate is met: do not draft those messages, do not ask "ready to send?", do not check website-confirm or #mission-control boxes.
Once the gate is met: ask the user to eyeball the website, then §6.5. Visual check is required before sending. 8.x "docs released" still includes the scrape line.
Propose next action
Summarize the §0.2 stage, what is still blocking, and the single next action. Do not offer a Slack ping or PR whose box is already checked / the user already sent.
1. Gather inputs
- Semver list.
- Docs release issue(s) --
[Docs release] X.Y.Z, not the eng release issue.gh issue list -R elastic/dev --search "[Docs release] <version>" --json number,title,url. - Eng release issue -- RC's parent (
[Release] X.Y.Z,X.Y.Z release, etc.). Canonical schedule (FF, GA), often links to docs issues, identifies the RC. Use for dates when the docs issue does not exist yet, which versions share a GA, and RC name. - If docs issues don't exist yet, §1.1.
1.1 Create docs release issues (when they don't exist)
- Template: 9.x minor →
docs-release.md; 9.x patch →docs-patch-release.md; 8.x →docs-patch-release-8.x.md. - Dates from the eng release issue (feature freeze, anticipated GA). If none, ask the user for those two only. Merge release notes by = the calendar day before anticipated GA (
[DAY_BEFORE_RELEASE_DATE]). Do not ask for merge-by or code freeze. gh issue create -R elastic/dev --template <template-file> --title "[Docs release] <version>"- Fill Overview (version, dates) and the release coordinator from the eng issue if listed. Leave the rest of the template as-is -- do not copy stakeholders from a previous release.
- Report: "Created #NNNN for X.Y.Z. Filled in dates from eng issue #MMMM. Please review."
- If the eng issue doesn't already reference the new docs issue:
gh issue comment <eng-issue> -R elastic/dev --body "Docs tracking: https://github.com/elastic/dev/issues/<new-docs-issue>"
User-provided schedule wins over the eng issue.
2. Classify each version
Patch if the patch number > 0; minor if V.R.0; major if V.0.0 (follow the dev issue + v8/v9 playbooks). Then route with §3.
3. Route PRs (decision)
8.x / 7.x (patch or minor)
Follow elastic-stack-v8.md. Coordinator PRs target elastic/docs only (shared/versions*, conf.yaml per template).
9.x -- isolated patch (only one 9.x in the batch, or no higher minor same GA)
- Patch: One
elastic/docs-builderPR: bumpconfig/versions.ymlsoversioning_systems.stack.currentmatches the minor line you ship (e.g.9.3.4-> minor 9.3). Link on theversions.ymlchecklist line. - Timing gate: Don't open this PR if more patch releases are expected on the same minor line before GA. If the current published version is more than one patch behind (e.g. current
9.3.2, shipping9.3.4), an intermediate patch is likely first -- wait or confirm with the user.
9.x -- minor (9.x.0)
Two separate draft PRs (two checklist lines):
| When | Branch / change | Link on dev issue |
|---|---|---|
| After FF | config/assembler.yml only: shared_configuration.stack.next -> upcoming minor (e.g. 9.4) |
Post-FF line -- PR or merge commit (shape) |
| Before GA (see timing below) | assembler.yml: stack.current -> that minor; stack.next -> main. versions.yml: versioning_systems.stack.current -> that minor. |
Nested versions.yml bullet -- PR URL at end of line |
Timing for the versions.yml PR (minors): Don't open early -- many patches land between FF and GA. Open when no more patches are expected before GA (typically 1-2 days before). Check #mission-control or ask if another patch is queued.
The coordinator merges each docs-builder PR when it is time; bump PRs and deploys are automatic. Do not substitute one PR for both post-FF assembler and pre-GA versions.yml steps.
Same calendar GA: multiple 9.x (e.g. 9.3.4 + 9.4.0)
Only one will be a minor -- the others are patches. There will not be two minors on the same day.
- Assembler PR: The minor still gets its own post-FF
assembler.ymlPR (stack.nextbump). - versions.yml PR: Only one -- targets the highest version in the batch.
- Canonical issue = highest 9.x minor (owns the versions.yml PR).
- Lower-line issue = lower semver patch: still for RNs; do not duplicate the versions.yml bump -- supersede to canonical (§4).
4. Edit elastic/dev issue bodies
- Main trail in the description, not comments.
- Do not delete template placeholders, footnotes, or stakeholder tables -- only add checkmarks, links, strikethrough, short cross-refs.
- Real
#/ PR URLs fromghor the user -- never guess numbers. - Check off as you go. After any skill-flow action that matches a checklist item, check it immediately. Nested boxes for the same action get checked together. Do not wait to be asked. Do not report the item as pending.
- Do not check items you did not complete. Other teams' steps (including docs-eng), user-completed work outside this skill, and future release-day items stay unchecked.
- After each coordinator PR this skill opens: check the parent and nested lines and paste the PR URL (minors: post-FF vs version-bump have different URLs on different lines).
Match by checklist wording, not by assuming one template:
| Skill action | Checklist line |
|---|---|
Outstanding-RNs / day-before #docs ping sent |
Post in #docs to remind ... PRs that have not been opened and/or approved |
| Coordinator PR opened | Open a PR ... plus the nested versions.yml / assembler.yml / conf.yaml line |
Merge-RN #docs ping sent |
Post in #docs ... they can merge their release note PRs |
| Coordinator PR merged | Merge the docs-builder config PR (or the 8.x equivalent) |
| Prod version-bump deploy succeeded (9.x only) | Watch for the prod version-bump deploy -- check only after §0.3 prod deploy success (staging did not fail). Do not use this line for 8.x. |
| User confirmed website eyeball | Confirm that the updated docs appear on the website -- only after the gate (§0.3) and the user says the site looks right |
#mission-control "docs are live" reply |
Post in #mission-control that the docs are live -- only after the gate and website eyeball; still confirm before sending Slack |
gh issue view <N> -R elastic/dev --json body -q .body > /tmp/dev-issue.md
gh issue edit <N> -R elastic/dev --body-file /tmp/dev-issue.md
Same-GA supersession (lower-line issue)
- Stakeholder blocks:
superseded by #<canonical>(real number). - Checklist blocks owned by canonical: wrap in
~strikethrough~, tag e.g.[not needed: superseded by 9.4.0 -- #<canonical>]. - Optional footer:
Crossed out instructions superseded by https://github.com/elastic/dev/issues/<canonical> - RN-only coordinator steps may stay open on the lower-line issue ("verify/publish this version's release notes"); strike only lines that duplicate canonical deploy work.
PR opened against the wrong issue's path: close with Superseded by owner/repo#N.
5. Open coordinator PRs (elastic/docs / elastic/docs-builder)
Branch: stack-<version> (e.g. stack-9.5-post-ff, stack-9.5, stack-8.19.16) -- no docs/ prefix, no pre-ga / "day before" in the name. Existing checkout if available; otherwise clone to a temp directory.
gh pr create -R elastic/<repo> --draft --base main --head <branch> \
--title "<concise title -- no Draft prefix>" \
--body "## Refs\n\n- https://github.com/elastic/dev/issues/<N>"
Mark ready only in the right window. Re-draft: gh pr ready <num> -R elastic/<repo> --undo.
Immediately after the PR exists: check the matching docs-issue lines and paste the PR URL (§4).
6. Reminders & messages (#docs, Slack)
Copy dates, issue links, products, and outstanding PR status from the docs issue (or user-provided schedule). Never invent versions, PR state, or @mentions.
If the body is not already in context from §0, gh issue view <N> -R elastic/dev --json title,url,body. Parse: version from title/Overview; anticipated GA / FF / merge-by from Overview; stakeholders and RN PR column from the Release Notes table. Skip vs blocking as in §0. URL present = blocking until merged.
Grouping: 8.x and 9.x tables often differ -- separate "Ping for ..." sections when they do; merge duplicate product lines when the same stakeholders apply to multiple versions.
Unresolved stakeholders and Slack send/preview: slack-templates.md.
Templates, mentions, send
Read slack-templates.md for templates, handle lookup, and OOO checks. If the user doesn't specify the reminder type, present options: feature freeze, outstanding RNs, docs released, or other.
Docs-released: one version → singular template (version / are released); two or more → plural (versions / are all released). Never "versions" / "all" for a single version. Omit the 8.x scrape line on a 9.x-only batch.
Send (do this even if you skip the templates file):
- Preview in a fenced code block (slack-templates.md) so line breaks are preserved. Then "Send?"
- Wait for confirmation. "Send it" / "looks good" counts. "Update the thread" / "check the boxes" does not.
- Payload mentions: people
<@U…>; user groups<!subteam^S…>. Never guess an ID. - Follow-ups: thread on the FF announcement (
docsFfThreadTs) andreply_broadcast: true. - After send: check the matching docs-issue item (§4).
6.5 Replying in #mission-control threads (release day)
Do not enter until the gate is met (§0.3): 9.x = blocking RNs merged + prod version-bump; 8.x = blocking RNs merged + elastic/docs coordinator PR merged. If not met, say what is outstanding and stop -- do not draft live/released messages.
Once the gate is met:
- Ask the user to eyeball the website (9.x: current docs / this version's release notes; 8.x: v8 playbook pages).
- After they confirm: check the "docs appear on the website" box (§4). Draft the
#mission-controlreply (X.Y.Z docs are live.) and the#docs"docs released" ping (8.x scrape line only when an 8.x version is in the batch). Show both; send only with confirmation (slack-templates.md). - On confirm: reply in the begin-publishing thread (
thread_tsfrom §0), send the#docsping, and check the#mission-controlbox.
7. Background poller (in-session)
When the user says "watch the PRs", "watch the deploy", or "let me know when they're all merged":
7.1 Release-note PRs
- Blocking RN PR URLs from the issue table (skip N/A / No changes only).
- Background shell (
block_until_ms: 0) every 5 minutes:gh pr view <url> --json state -q .state. - All
MERGED→ emitALL_RN_PRS_MERGED(notify_on_output). - Report: "All RN PRs are merged." Persist to the session file.
7.2 docs-internal-workflows deploys (9.x only)
Skip for 8.x / 7.x.
After the 9.x docs-builder coordinator PR is merged (or when the user asks to watch 9.x deploys):
- Config SHA + staging/prod bump PRs + version-bump run IDs (§0.3). Prefer session state.
- Poll
gh run view <id>every 30s. EmitDEPLOY_STAGING_FAILED(blocks; alert immediately),DEPLOY_PROD_FAILED,DEPLOY_PROD_DONE(prodcompleted/successand staging did not fail). - On
DEPLOY_PROD_DONE: "Prod deploy succeeded." If blocking RN PRs are still open, list them -- do not offer live/released Slack. If they are all merged, ask the user to eyeball, then §6.5. - Write run IDs and conclusions to session state.
Pollers only run in the current session. Next session: §0.1 + §0, not full rediscovery.
For anything not specified here (API docs, Buildkite): follow the docs issue checklist and playbooks.