The step that dispatches to MSIME-Web was gated on `github.event_name == 'workflow_dispatch'`. This workflow's own header documents workflow_dispatch as the repair path and a push to main as the normal one, so the notification only fired on the branch that almost never runs. MSIME-Web has received zero repository_dispatch events; its most recent twenty sync runs are all `schedule`.
That left the site waiting on a cron GitHub delivers about seven times a day instead of the forty-eight the schedule asks for, with measured gaps of two to five hours. Windows 0.8.0 published on 2026-09-20 and msime.app still offered 0.7.1 a day later.
The enclosing job already runs only when a release is being published, so the step needs no event condition at all.
The Server job's Build step dominates the pipeline (~6m37s of a ~9.5m job;
the dictionary/integration test is only ~43s). sccache caches individual
MSVC compiles across runs so an incremental change rebuilds only what it
touched instead of the whole server + engine tree.
sccache only caches an MSVC compile whose debug info is embedded in the
object, so switch the Server build's Release debug info from /Zi to /Z7.
The linker's /DEBUG still emits the beside-the-executable PDB the release
job validates, so the symbol layout is unchanged.
Its compiler-launcher hook is honoured only by the Ninja and Makefile
generators, so configure the Server build with Ninja Multi-Config (keeping
the --config Release / bin/Release layout the later steps depend on) and
add an MSVC-environment step, since Ninja does not locate cl.exe on its own.
The three self-hosted release jobs are separate runner instances on one
machine, so they share C:\Users\runner\.gitconfig and all reach "Trust the
runner workspace" in the same second. Two win the config lock and the third
dies with "could not lock config file ...: File exists", taking Build TSF x64
down before it compiles anything and skipping the installer job.
Retry the write, and re-read safe.directory before each attempt so a peer
instance having already trusted this path counts as success.
Two independent failures kept the v0.6.5 installer from being built after
the pipe probe was fixed.
The package job died in "Verify the release commit is on main" with git
exiting 128 on dubious ownership: runner instance 2 leaves _work owned by
SYSTEM while the job runs as `runner`. actions/checkout is unaffected, so
the first plain git call is what fails, and only jobs scheduled onto that
instance see it. Mark the workspace as safe in the three self-hosted jobs.
Neither vcpkg manifest carried a builtin-baseline, so vcpkg resolved ports
against the registry at HEAD and every build depended on a live fetch of
microsoft/vcpkg. When that fetch degraded, the ports simply vanished:
Fetching registry information from https://github.com/microsoft/vcpkg (HEAD)...
fmt does not exist
utfcpp does not exist
Build TSF x64 failed this way twice and passed twice on the same runner,
which is what an unpinned registry looks like. Pin both manifests to
04a9d8e5, the commit vcpkg-lock.json already records as resolved.
The Hold-for-review step built its comment with printf and spliced ${{ steps.classify.outputs.reason }} into the command text inside single quotes. That value is the list of file names the engine changed. GitHub substitutes ${{ }} textually before bash parses the line, so a path containing a single quote closes the quoting and the rest runs as a command — on a runner holding a contents:write and pull-requests:write token for this repository. Merge rights in MSIME-Engine became command execution here. Non-adversarially the same splice makes the step die under set -euo pipefail on any engine path containing an apostrophe, so a routine bump gets a red triage job and no review comment.
Every value now reaches the shell through env: and the comment body goes through --body-file. The $GITHUB_OUTPUT heredoc that carries those same file names gets a random delimiter, since a path equal to the literal EOF would otherwise close the block early.
Separately, the classifier could auto-merge a range it had not inspected. With an empty file list, echo emits one blank line, grep -v prints it, and command substitution strips it, so the emptiness test read "nothing suspicious changed" and enabled auto-merge. The compare API returns an empty file list whenever head is already an ancestor of base — an engine force-push, or the pin sitting ahead — which is exactly the case where a bump silently reverts engine source this frontend depends on. It now fails closed on any compare status other than a plain fast-forward, and on an empty file list.
Found while auditing MSIME-Apple, which carried a byte-identical copy of this workflow. Fixed there in metasequoiaime/MSIME-Apple#269.
Dependency review was guarded on the pull_request event, which made it report "skipped" on every other trigger. A required status check that reports "skipped" blocks a merge rather than passing it, and #166 made this one of the nine contexts main requires, so the automatic release could no longer land its own release pull request: land-release-pr.sh watched ci.yml go green and then got "the base branch policy prohibits the merge" (run 34004706430, pull request #173).
The guard cannot simply move to the release path either. ci.yml ignores the release pull request by path, because a pull_request run started by GITHUB_TOKEN is created in action_required and never executes, so the release workflow dispatches ci.yml on the release branch and that dispatch is the only run reporting checks on that commit.
The job now runs on every trigger. A pull_request event supplies both refs on its own; every other trigger compares the default branch against the checked-out commit, which is the same question the pull request asks.
GitHub has three release states and no notion of a channel, so an automatic per-merge build and a release somebody deliberately cut both land in the same Pre-release list that docs/installation.md presents to users as the 内测版 channel. Before every releasable merge produced a release that did not matter; now it does.
Releases published from a push get 「(自动构建)」 in the title and a note saying they were not curated. Releases published from a workflow_dispatch keep the bare tag as their title, which is what release-please already set. docs/installation.md tells readers which is which and why they share one list.
This is deliberately a labelling change and not a second release track. Giving automatic builds their own tag namespace would need a parallel versioning mechanism alongside the one release-please owns, and stacking that on a pipeline that is currently failing and shipping unsigned artifacts is the wrong order.
Refs MSIME-Windows#167
A push to main carrying a fix or a feat now ends with a published installer, with no human merging the release pull request and no manual workflow_dispatch in between.
release-please only cuts a release for a release pull request that was already merged when it ran, so the prepare job now invokes it twice: once to refresh the pull request against the commits this push brought, and once after land-release-pr.sh has merged it, to turn that merge into the draft the rest of the run builds and publishes. Merging before the first invocation would instead release everything except the push that triggered the run.
land-release-pr.sh is check-release-pr.sh plus the merge it deliberately used to skip. It merges with GITHUB_TOKEN, whose pushes do not start workflow runs, so the merge cannot produce a second Release run queued behind this one on the concurrency group; the release is created and built inside the run that merged it. It merges the exact commit CI proved via --match-head-commit, then waits for that merge commit to be reachable from main so the second release-please invocation does not quietly find nothing to release.
resolve-auto-release.sh picks which of the two invocations reported the release to build, preferring the second when both did, and applies the invariants validate-draft-release.sh gives the manual path: the release is tagged at a full commit SHA, and the tag agrees with the version.
workflow_dispatch stays as a repair path for a run that failed after creating its draft.
This spends one of the signing certificate's limited signatures on every releasable merge, which is what the previous manual design existed to avoid. docs/product-release.md records the trade, including why the queueing this can produce is not answered by cancel-in-progress.