Harden Skia-sync conflict policy, allow durable gn args, and split skill vs workflow#4258
Merged
Merged
Conversation
Let the Skia sync workflow and update-skia skill target a release/<major>.<milestone>.x line (in both mono/SkiaSharp and mono/skia) instead of only main, derived automatically from the resolved milestone. - auto-skia-sync.md: pre-activation detects the release line from the target milestone, exposes is_release/base_branch/skia_base_branch/ head_branch, fails fast if the mono/skia release branch is missing, and adapts the skip-check; host submodule-align step and prompt body use the resolved base/head branches. - skia-sync-push-prs.sh: consume HEAD_BRANCH/BASE_BRANCH/SKIA_BASE_BRANCH/ IS_RELEASE and target the right base branches with release-aware titles. - update-skia skill: document release-target mode (Phase 1 base detection, parametric Phase 4 + Phase 11, fail-if-missing mono/skia branch). Creation of the matching mono/skia release branch is owned by the release process and will be handled in a separate change to the release-branch skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the milestone/branch-line resolution out of the inline workflow YAML and into two committed, testable scripts: - skia-sync-detect.sh: the single source of truth for mode/target/base-branch resolution. Emits key=value to $SKIA_SYNC_OUT (default $GITHUB_OUTPUT); --gate adds the upstream-exists / already-merged checks. Run by both the pre_activation step (--gate, writes job outputs) and the agent align step (sourced, since that job can't read pre_activation outputs). - skia-sync-align-submodule.sh: the submodule alignment, driven by the sourced base_branch / skia_base_branch. The pre_activation job now sparse-checks-out .github/scripts so it can call the script. This collapses the two large inline bash blocks to ~1 and ~5 lines and removes the duplicated branch-resolution logic. Behaviour is preserved for the main line and the single-match release path. The extraction also folds in two release-resolution fixes from review that were not yet in the previous commit: the strictly-older check is now a numeric compare (`-lt` instead of `!=`), and a milestone matching multiple release branches now hard-fails instead of silently picking the first via `head -1`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an engine: block pinning model claude-opus-4.6 (overriding the default claude-sonnet-4.6). Upstream merge/conflict resolution and native build-fix reasoning are hard, so the stronger model is worth the higher AI-credit cost for this workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Opus 4.7 carries the same 5x AI-credit multiplier as 4.6, so move to the newer model for the upstream merge/conflict-resolution reasoning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
native/linux/build.cake builds libSkiaSharp/libHarfBuzzSharp with -stdlib=libc++. On the real CI that runtime comes from the .NET cross-compilation image, but this workflow builds on a bare Ubuntu host where the agent cannot apt-install anything (no apt/sudo in the AWF chroot, and the firewall blocks the OS package mirrors). Install libc++-dev + libc++abi-dev in the host pre-agent step so the unmodified native build compiles, and tell the agent not to patch native build files or compiler flags to work around a missing dependency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The cron→mode mapping (7 AM → current, 5 PM → latest, else → next) was the only workflow-coupled knowledge inside skia-sync-detect.sh — it hardcoded the literal cron strings and read mode/milestone/schedule from INPUT_MODE, INPUT_MILESTONE and SCHEDULE env vars. Move that mapping into the workflow, next to where the crons are declared, as a single GitHub Actions expression. The detector now takes real --mode/--milestone arguments and no longer knows anything about cron, so it is self-contained and unit-testable (pass --mode latest instead of faking SCHEDULE='0 17 * * *'). mode is staged into an env var (not interpolated straight into run:) so the free-form milestone dispatch input cannot inject shell — the same anti-injection safety the previous env-based wiring had. Behavior is unchanged: validated that --mode current/next/latest, the default, an explicit --milestone override, an empty --milestone, and an unknown arg all resolve exactly as before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Final review pass over the skia-sync scripts: - Harden skia-sync-detect.sh arg parsing: a value-less --mode/--milestone now reports a clear error and exits 2 instead of tripping set -e on a double shift. Matters now that the detector is meant to be run by hand for testing. - Flatten the multi-match release-branch list with paste instead of the $(echo $VAR) word-splitting idiom (SC2116/SC2086). - Mark the runtime-generated skia-sync-env.sh source with a shellcheck source=/dev/null directive (SC1091). All three scripts are now shellcheck-clean at style level; behavior verified unchanged for every valid invocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…release-branch # Conflicts: # .github/scripts/skia-sync-detect.sh # .github/scripts/skia-sync-push-prs.sh # .github/workflows/auto-skia-sync.lock.yml # .github/workflows/auto-skia-sync.md
The first `mode=main` (tip) run (PR #4254 validation) surfaced two rough edges in the upstream-sync automation worth fixing before tip-mode becomes routine. ~~ Verify-upstream-or-reapply conflict resolution ~~ A tip merge is ~291 commits ahead of our submodule base, so it conflicts on every file that carries a `[M1xx]` fork cherry-pick. The old guidance was a blunt "keep ours" / "resolve what you reasonably can", which risks two failure modes: silently dropping a not-yet-upstreamed fork patch with `--theirs`, or freezing a stale form of a patch upstream has since refined with `--ours` (e.g. our SDF-LCD patch used `getMaxScale()`; upstream relanded it as `sk_ieee_float_divide(1.f, getMinScale())`). Replace it with an explicit, auditable policy: for each conflicted file, classify every fork patch as *upstreamed* (take upstream's refined form) or *not upstreamed* (re-apply our patch on top of upstream) — never a blanket `--theirs`/`--ours`, never a silent drop. A mandatory before-merge snapshot of fork patches must be cross-referenced so every fork patch on a conflicted file lands in the PR's "Conflicts resolved" table as upstreamed or re-applied. A patch that is neither is a lost patch — stop and fix it. Applied in gotcha #15, SKILL.md Phase 5, and the workflow's tip-mode note. ~~ Allow a required new gn arg through build.cake ~~ The tip build needed `skia_use_partition_alloc=false` because upstream now defaults `skia_use_partition_alloc=is_clang` while our DEPS deliberately disables `third_party/externals/partition_alloc` (Skia ships an official zero-overhead noop `raw_ptr` for exactly this case). The agent passed it as a one-off `--gnArgs` CLI flag because the rule banned all `build.cake` edits — durable config ending up non-durable. Relax the rule into two tiers: host/flag hacks to silence a build error stay FORBIDDEN (they break the platforms the Linux-only sync never builds), but a genuinely *required* new upstream gn arg may be added to the affected platforms' `native/**/build.cake` gn-args lists (the single source of truth, next to the existing `skia_use_*` toggles) and flagged in both PR summaries for cross-platform human review. This lets the next tip merge bake the toggle in durably — and correctly ships it alongside the milestone-bearing submodule, so it never breaks the current m150 build (which has no such gn arg yet). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
📦 Try the packages from this PRWarning Do not run these scripts without first reviewing the code in this PR. Step 1 — Download the packages bash / macOS / Linux: curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 4258PowerShell / Windows: iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 4258"Step 2 — Add the local NuGet source dotnet nuget add source ~/.skiasharp/hives/pr-4258/packages --name skiasharp-pr-4258More options
Or download manually from Azure Pipelines — look for the Remove the source when you're done: dotnet nuget remove source skiasharp-pr-4258 |
Contributor
|
📖 Documentation Preview The documentation for this PR has been deployed and is available at: 🔗 View Staging Site This preview will be updated automatically when you push new commits to this PR. This comment is automatically updated by the documentation staging workflow. |
… workflow
The auto-skia-sync workflow prompt had accumulated general Skia-update
knowledge that belongs in the update-skia skill, not in CI-specific prose.
Two concepts in particular were documented ONLY in the workflow, so the
skill couldn't drive a local/manual run of them:
1. `main`/tip mode — syncing from the very tip of google/skia `main`
(HEAD) instead of a `chrome/m{N}` milestone branch. Bleeding-edge,
`CURRENT == TARGET`, NOT a version bump, but may carry new APIs.
2. The "a required new upstream gn arg goes in native/**/build.cake,
not a one-off --gnArgs flag" rule (e.g. skia_use_partition_alloc
=false when our DEPS doesn't vendor partition_alloc).
Move both into the skill so it works standalone, and reduce the workflow
to minimal CI-specific overrides + pointers (option B: duplicated
reminders kept as short safety one-liners, since the unattended CI agent
benefits from repeated guardrails).
Skill (works locally now):
* SKILL.md Phase 1: tip mode is a first-class third option alongside
main-target and release-line-target — variable table gains a
{UPSTREAM_REF} row and a tip column, plus a paragraph covering the
not-a-version-bump semantics and how Phase 2/5 milestone diffs become
merge-base..upstream/main for the tip.
* SKILL.md Phase 5: merge command generalized to upstream/{UPSTREAM_REF}.
* SKILL.md Phase 7: new error-table row + note for a required new gn
arg, cross-referencing the gotcha.
* known-gotchas.md: new gotcha #23 — partition_alloc worked example
(why our DEPS un-vendors it, why the noop raw_ptr / =false is the
supported embedder path, not vendoring) and the milestone-sequencing
caveat (the arg doesn't exist before its upstream branch point, so
only add it alongside the submodule that carries it).
Workflow (body-only edits; lock stays byte-identical, frontmatter
unchanged):
* Mode/tip note and the FORBIDDEN/ALLOWED build.cake block collapsed
to short pointers into the skill, keeping the genuinely CI-specific
parts: can't apt-install in the sandbox, a missing host dependency is
a workflow bug (fix the install step, don't hack shared flags), and
the Linux-only-build cross-platform review reminder.
gh aw compile auto-skia-sync: 0 errors, 4 pre-existing warnings, lock
unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Follow-up on the skill/workflow split (commit 35ee068): an Opus 4.7 read-only review of SKILL.md + auto-skia-sync.md flagged two live bugs and several consistency gaps left over from the release-line/tip-mode refactor. The workflow points the agent straight at named phases/steps in the skill, so a hardcoded `origin/main` there silently does the wrong thing on a release-line or tip sync. Fixes: Blocking (live refactor misses — `origin/main` should follow the resolved base): * Phase 9 step 1: diff unwrapped bindings against `origin/{BASE_BRANCH}`, not `origin/main`. * Phase 5 "before proceeding" gate: describe the parent/submodule base as `origin/{BASE_BRANCH}` instead of `origin/main`. Should-fix (sync/consistency): * Phase 5 step 5 source-file verification: parameterize the milestone-pair diff to `$(git merge-base {SKIA_BASE_BRANCH} upstream/{UPSTREAM_REF}).. upstream/{UPSTREAM_REF}` so it works for tip mode too. * Phase 10 test suite: stop leaking the workflow-only `/tmp/gh-aw/agent` path into the standalone skill — use `/tmp/skia-test-output.txt` and note that the automated workflow overrides it to the artifact path. Mirror the override note in the workflow's Phase 10 file-writing section. * Phase 1 step 5: restructure the three modes (main target / release-line target / `main` tip) into clearly-labeled blocks; the closing "use these values" line now lists `{UPSTREAM_REF}` and `chrome/m{TARGET}` so the merge-from ref is covered. Name Phase 2 step 4 as the only milestone-pair diff needing tip substitution (Phase 5 already uses `{UPSTREAM_REF}`). * Phase 4 preamble and Phase 11 branch-target note: add `main`/tip mode to the per-mode branch examples. Body-only edits: `gh aw compile auto-skia-sync` = 0 errors and the `.lock.yml` is byte-identical (the lock embeds only frontmatter). Gotchas are intentionally not renumbered per the reviewer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A successful upstream-sync run was being mislabeled a "no-op" in the gh-aw
conclusion job, and the job then tried (and failed) to file a "no-op runs"
issue. Both were misleading: the run had actually merged upstream Skia, built,
tested, and produced two real PRs.
The cause is the two-subsystem split this workflow uses. Real GitHub writes
(push both repos, open both PRs) happen in the custom post-step
`skia-sync-push-prs.sh` with SKIASHARP_AUTOBUMP_TOKEN — gh-aw itself can't open
the mono/skia PR because the submodule's merge commits live in a nested repo it
only sees as a gitlink, and `safe-outputs: staged: true` deliberately blocks the
agent from creating anything directly. With only `noop` available as a
completion signal, every run — including real syncs — signalled `noop`, and the
conclusion job classified on that alone.
Give the agent an honest signal instead:
* Declare `create-pull-request` in safe-outputs, kept STAGED (preview-only:
no real PR, no branch push, no patch — the safe_outputs job runs on
ubuntu-slim with `permissions: {}` and never touches the workspace). The
agent calls it as its completion signal when it did work, so the run
registers as a pull-request output rather than a no-op. Real PRs are still
opened only by the post-step.
* Set `noop: report-as-issue: false` (the gh-aw default is true). `noop` is
now reserved for genuine no-work runs and no longer tries to file an issue
the workflow has no `issues: write` permission to create.
* Update the prompt's "Completion signal" guidance: call `create_pull_request`
when `skia-sync-env.sh` was written, `noop` only when it was not.
`create_issue` is no longer offered to the agent (it was never used — the prompt
already forbade it). `gh aw compile` = 0 errors; the 4 pre-existing warnings
(3 fixed-schedule + storage.googleapis.com) are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
Follow-up to the
mode=main(tip) work (#4254) and release-line targeting (#4239). The first real tip run surfaced two rough edges in the upstream-sync automation and exposed that a chunk of general Skia-update knowledge lived only in the workflow prompt (so theupdate-skiaskill couldn't drive it locally). Then, watching real runs, a third rough edge appeared: the conclusion job mislabeled successful syncs as "no-op".Sections 1–3 are docs/prompt-only (no product code; prompt-body edits, so
frontmatter_hashis unchanged for those). Section 4 is the one workflow-config change — it edits the workflow frontmatter (safe-outputs), so unlike the prompt-body edits it does regenerateauto-skia-sync.lock.yml.1. Verify-upstream-or-reapply conflict resolution (do it right, don't pick a side)
A tip merge is ~291 commits ahead of our submodule base, so it conflicts on every file carrying an
[M1xx]fork cherry-pick. The old guidance ("keep ours" / "resolve what you reasonably can") risks two failure modes:--theirs→ silently drops a not-yet-upstreamed fork patch.--ours→ freezes a stale form of a patch upstream has since refined (e.g. our SDF-LCD patch usedgetMaxScale(); upstream relanded it assk_ieee_float_divide(1.f, getMinScale())).Replaced with an explicit, auditable policy: for each conflicted file, classify every fork patch as upstreamed (take upstream's refined form, record the SHA) or not upstreamed (re-apply our patch on top of upstream) — never a blanket
--theirs/--ours, never a silent drop. A mandatory before-merge snapshot of fork patches is cross-referenced so every fork patch on a conflicted file lands in the "Conflicts resolved" table as upstreamed or re-applied. A patch that is neither is a lost patch — stop and fix it.Applied consistently in
known-gotchas.md(gotcha #15, rewritten),SKILL.md(Phase 5 conflict step + audit), andauto-skia-sync.md(tip-mode note).2. Allow a required new gn arg through build.cake
The tip build needed
skia_use_partition_alloc=falsebecause upstream now defaultsskia_use_partition_alloc=is_clang, while ourDEPSdeliberately disablesthird_party/externals/partition_alloc. Skia ships an official zero-overhead noopraw_ptrfor exactly this case (MiraclePtr fallback "because partition_alloc dependency is missing"), so=falseis the supported embedder path — not a hack. The agent had to pass it as a one-off--gnArgsCLI flag purely because the rule banned allbuild.cakeedits, leaving durable config non-durable.Relaxed the rule into two tiers:
scripts/infra/native/**) to silence a host build error — those break the platforms the Linux-only sync never builds.native/**/build.cakegn-args lists (the single source of truth, next to the existingskia_use_*toggles), flagged in both PR summaries for cross-platform human review.Sequencing matters:
skia_use_partition_allocdoes not exist in m150, so baking it in now would break the current build with "unknown gn arg." The relaxed rule lets the next tip/milestone merge add the toggle in-context — shipping it alongside the milestone-bearing submodule, never before.partition_alloc recommendation (for the eventual m151 bump): keep it disabled via the noop (
=false) baked intobuild.cake, rather than vendoring the Chromium allocator. The noop is Google's own supported path; our threat model ≠ Chrome's (UAF mitigation has marginal value for a library in trusted .NET apps); vendoring adds a large Chromium dep + binary bloat + ongoing DEPS-roll maintenance across our full platform matrix. Even upstream has flip-flopped on the default. Revisit only if we ever want raw_ptr hardening and accept the cross-platform dependency.3. Make the skill standalone; reduce the workflow to CI-specific overrides
The workflow prompt had accumulated general Skia-update knowledge that belongs in the
update-skiaskill, not in CI prose. Two concepts were documented only in the workflow, so the skill couldn't drive a local/manual run of them. Moved both into the skill (so it works standalone), and trimmed the workflow to minimal overrides + pointers — keeping the remaining duplicated reminders as short safety one-liners, since an unattended CI agent benefits from repeated guardrails.Into the skill (now works locally):
main/tip mode is a first-class third option in Phase 1 (alongside main-target and release-line-target): the variable table gains a{UPSTREAM_REF}row + a tip column, plus a paragraph on the not-a-version-bump semantics and how Phase 2/5 milestone diffs becomemerge-base..upstream/mainfor the tip. Phase 5's merge command is generalized toupstream/{UPSTREAM_REF}.Out of the workflow (body-only edits → lock byte-identical): the mode/tip note and the FORBIDDEN/ALLOWED
build.cakeblock collapse to short pointers into the skill, keeping only the genuinely CI-specific parts — can't apt-install in the sandbox, a missing host dependency is a workflow bug (fix the install step, don't hack shared flags), and the Linux-only-build cross-platform-review reminder.4. Stop mislabeling a real sync as a "no-op" (honest completion signal)
Watching the first live runs, a sync that actually merged upstream, built, tested, and opened both PRs was reported by the gh-aw conclusion job as a no-op — which then tried (and failed, the workflow has no
issues: write) to file a "no-op runs" issue. Misleading on two counts: a PR-producing run is not a no-op, and the failed issue step added red noise.The cause is the two-subsystem split this workflow uses on purpose.
safe-outputs: staged: trueblocks the agent from creating anything directly; the real GitHub writes (push both repos, open both PRs) are done by the host post-stepskia-sync-push-prs.shwithSKIASHARP_AUTOBUMP_TOKEN— gh-aw can't open the mono/skia PR itself because the submodule's merge commits live in a nested repo it only sees as a gitlink. Withnoopas the agent's only available completion signal, every run — including real syncs — signallednoop, and the conclusion classified on that alone.Give the agent an honest signal instead:
create-pull-requestinsafe-outputs, kept staged (preview-only: no real PR, no branch push, no patch — the safe_outputs job runs onubuntu-slimwithpermissions: {}and never touches the workspace). The agent calls it as its completion signal when it did work, so the run registers as a pull-request output rather than a no-op. Real PRs are still opened only by the post-step.noop: report-as-issue: false(the gh-aw default istrue).noopis now reserved for genuine no-work runs and no longer tries to file an issue the workflow can't create.create_issueis no longer offered to the agent (it was never used — the prompt already forbade it).create_pull_requestwhenskia-sync-env.shwas written,nooponly when it wasn't.Unlike sections 1–3, this edits the workflow frontmatter, so
auto-skia-sync.lock.ymlis regenerated viagh aw compile(safe-outputs config + the #4260 name; never hand-edited).Rebased on main
Picked up #4260 (regroup/rename GitHub Actions workflows), which renamed this workflow
Skia Upstream Sync→Sync - Skia Upstream. The merge was clean andgh aw compilereproduced the lock with a 0-diff, so the rename and the Section-4 safe-outputs change coexist correctly. Also confirmed #4267's new support-status model (versions.json: stable4.148, preview4.150; the SkiaSharp minor number is the Skia milestone) is consistent with the skill'srelease/<major>.<milestone>.xmapping — no change needed.Validation
gh aw compile auto-skia-sync→ 0 errors (4 pre-existing warnings).auto-skia-sync.lock.ymlis regenerated and committed (this supersedes the earlier "no lock change / 3 files" note).mode=main, real 2-commit merge): the agent emittedcreate_pull_request(staged, title[skia-sync] Update Skia to upstream main HEAD (e47bb0549c)); the safe_outputs job logged0 noop message(s)and wrote a staged PR preview; the conclusion loggedNo noop items foundwith no failed issue step; and the host post-step still pushedskia-sync/mainto both repos and updated the real PRs ([skia-sync] Merge upstream Skia main (tip) #4263 + Making the Forms views more extendable #269). All jobs green.SKILL.md,references/known-gotchas.md,auto-skia-sync.md,auto-skia-sync.lock.yml.