Dashboard › craft › Distillation
9e10a0a1-d55a-4c70-b1b6-42eb9bd45060["lore_tm_v1_UWp0qEyOT4KCeaSOfbQ0tBgv-oY7eMMfr1wKOy59cL0","lore_tm_v1_g31pCZaIJ_R2k3W44WBHGJaSANeuoWaCg3wX-qy_frQ","lore_tm_v1_PvUv2U4mleZcaONfIIaLDeH7_TD27J-YPmyu5x78Bps","lore_tm_v1_fqgbTVoSQ_cOiIwLJxUPgK3cu8RGXpACcOj8xun1wTo","lore_tm_v1_g1UvD0db2QV3B_-IZqLMFCdJjSiorFAw4gCzcNqf8qc","lore_tm_v1_9DSGNxPfHYlsdQFUBtag_lz1pYnuMuXk5W8ZbtEdx7Q","lore_tm_v1_-vUNhjqvIjJ8W4O9PrxUQT27UKd83IYGtLczaIpLxJ8","lore_tm_v1_DDDdiF_hDV62beZ8p8ypU39CCVreKlv3eaO4CRaVNHQ","lore_tm_v1_TUG8fu_PnVXNXgkm4h8EZ4mREBlH4hc5igRuP2_DNSI","lore_tm_v1_q23dXKTr9OWFvPb2uQOHO6Zu_m1pEqBGVAgMa8TslVM","lore_tm_v1_eFyYZbJZvpI0zvzWds_Gmaq0fRrVjRAvqygqLoY0X3c","lore_tm_v1_TmwVjDkdOW59QCP3A3ZHcZP83zd1IFJnnCyY_sBYUIE","lore_tm_v1_d9r7zkjbgakKS-IXYGn_ioYmgkml2WL0_8Wb_y7C8Qc"]
Date: Sep 8, 2026
workflow_dispatch for manual recovery. The .github/workflows/ci-poller.yml job gate is vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'.ci-ready after CI passes; waiting-for-ci first removes existing ci-ready before adding ci-pending, ensuring the later fresh ci-ready labeled event triggers publishing..github/workflows/ci-poller.yml polls only open issues carrying both ci-pending and accepted labels, up to 200 issues, so abandoned/unapproved releases are not polled.success or there are 0 reported statuses; 0 pending check runs; and 0 completed check runs with conclusions other than success, neutral, or skipped.ci-pending, adds ci-ready, and comments CI checks passed for ${repo}@${version}. Publishing is starting now. On failed completed check runs, it removes ci-pending and accepted, adds ci-failed, comments failed check names/check-runs URL, and requires re-adding accepted after CI is fixed. On failed commit status with otherwise successful check runs, it performs the same label changes and comments failing status contexts.sentry-internal-app (SENTRY_INTERNAL_APP_ID / SENTRY_INTERNAL_APP_PRIVATE_KEY) for label changes that trigger downstream publish.yml, and sentry-release-bot (SENTRY_RELEASE_BOT_CLIENT_ID / SENTRY_RELEASE_BOT_PRIVATE_KEY, owner getsentry) for cross-repository check-suites, status, check-runs, and refs because the internal app is not installed on some private repositories including sentry-xbox, sentry-playstation, sentry-switch, and service-registry.repos/${repo}/git/ref/heads/${branch}; if either API query fails or produces no value, it falls back to the issue SHA. It updates the canonical issueβs check-runs revision when branch HEAD differs from the issue SHA.ci-pending + accepted issues even after CI-check failure (if: always()), then syncs CI_POLLER_HAS_PENDING to "false" when none remain or "true" otherwise, using the dedicated CI_POLLER_APP_CLIENT_ID / CI_POLLER_APP_PRIVATE_KEY token when token creation succeeds.gh workflow run ci-poller.yml -f attempt="${attempt}" while issues remain pending, with a 60-attempt cap. It relies on GitHub Actions startup latency for roughly 30β60-second rechecks because */5 cron can drift to 30β40 minutes under load; after 60 attempts it falls back to cron until an accepted-label re-addition or manual workflow_dispatch restarts the chain..github/workflows/publish.yml waiting-for-ci runs only when an open publish: issue receives accepted; it idempotently removes ci-failed and ci-ready, adds ci-pending, comments either retry or approval status, best-effort sets CI_POLLER_HAS_PENDING=true, and immediately triggers ci-poller.yml with the internal-app token because GITHUB_TOKEN-created workflow_dispatch events are suppressed..github/workflows/publish.yml publishes only on an open issueβs ci-ready label event when labels include accepted and ci-ready but exclude ci-pending and ci-failed; the publish job has timeout-minutes: 90.getsentry/${{ fromJSON(steps.inputs.outputs.result).repo }} at steps.release-revision.outputs.revision with fetch-depth: 0 and release-bot token, then runs discover-location.js; Craft publishes from __repo__/${{ fromJSON(steps.location.outputs.result).path }} using craft publish <version> --rev <CI-approved revision>.Set targets step creates a Craft publish-state file outside __repo__/ under $GITHUB_WORKSPACE/.craft-state/craft. It validates the resolved container cwd stays inside /github/workspace/__repo__, uses the first 12 characters of its SHA-1 as cwd_hash, lowercases/sanitizes owner/repo/version components, base64url-encodes unsanitized version values and workspace names, and writes {"published": ...} state before Craft runs.discover-location.js exports getWorkspaceNames({ repositoryDirectory, exists = existsSync, execFile = execFileSync }): it returns [] when <repositoryDirectory>/.craft.yml is absent; otherwise runs docker run --rm --volume <absolute repositoryDirectory>:/github/workspace/__repo__ --workdir /github/workspace/__repo__ getsentry/craft:latest workspace list, requires JSON output to be an array, and otherwise throws Craft workspace discovery returned an invalid workspace list..discoverLocation({ input, repositoryDirectory, exists, execFile }) resolves input.path against discovered workspace names via resolvePublishLocation; main() requires nonempty input.path (Publish input must define a path.), defaults PUBLISH_REPOSITORY_DIRECTORY to __repo__, and JSON-stringifies its result workflow output.src/modules/ci-poller-input.js parses and validates title repository, version, and .${parsedTitle.path}; derives the canonical release revision from the issue body; returns { repo: "getsentry/${repo}", revision, version }; and, when a replacement revision is supplied, additionally returns issueBody with only the canonical revision changed via updateReleaseRevision.src/publish/__tests__/discover-location.js verifies absent root .craft.yml retains checkout path { path: "./packages/cli" }; discovered workspace ["packages/CLI"] resolves ./packages/CLI to { path: ".", workspace: "packages/CLI" }; and invalid Craft JSON object {} or blank output "\n" fails closed with Craft workspace discovery returned an invalid workspace list..src/publish/__tests__/resolve-location.js verifies resolve-location.js with PUBLISH_ARGS: "" and CRAFT_WORKSPACE_NAMES: "[]" exits nonzero with Invalid publish path. rather than Unexpected end of JSON input.src/publish/__tests__/resolve-release-revision.js verifies resolve-release-revision.js with PUBLISH_ARGS: "" and empty PUBLISH_ISSUE_BODY exits nonzero with Publish input must define a repository. rather than Unexpected end of JSON input.src/modules/__tests__/ci-poller-workflow.js extracts the CI-check shell script from .github/workflows/ci-poller.yml, runs it with mocked node, mktemp, and gh, and uses INITIAL_REVISION = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" and UPDATED_REVISION = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".gh issue edit; rewrite responses that are missing issueBody, contain empty issueBody, are malformed JSON (not JSON), or are non-object JSON ([]) skip editing; all such cases exit successfully and leave no temporary files. A preservation test verifies canonical issue body\n\n is written identically through --body-file and temporary files are removed.src/modules/__tests__/generate-publish-issue-title-parser.js verifies generateDocumentation throws Could not find the generated title grammar in the documentation. when title-grammar markers are absent, duplicated, or malformed (<!-- BEGIN GENERATED TITLE GRAMMAR -- >).