ci migrate: edit workflows in place instead of re-encoding them - #555
Open
robstolarz wants to merge 1 commit into
Open
ci migrate: edit workflows in place instead of re-encoding them#555robstolarz wants to merge 1 commit into
robstolarz wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ce034bd. Configure here.
robstolarz
force-pushed
the
rob/dep-5306-migrate-yaml-fidelity
branch
from
August 14, 2026 18:25
ce034bd to
1f5844c
Compare
robstolarz
force-pushed
the
rob/dep-5306-migrate-yaml-fidelity
branch
from
August 14, 2026 21:32
1f5844c to
8bbbb7d
Compare
robstolarz
changed the base branch from
rob/dep-5812-migrate-no-path-rewrite
to
main
August 14, 2026 21:32
Migration parsed each workflow with yaml.v3 and re-encoded the node tree. That reformats the whole file: every blank line disappears, and a `run: |` block whose lines end in whitespace loses its block style entirely and comes back as one `\n`-escaped single-line string. Neither is a change the user asked for, and both land in the diff they have to review. Parse to locate, not to rewrite. The node tree is now used only for the line and column of the things migration actually changes; each change is spliced into the original bytes as a text edit. What is not edited is not touched, so blank lines, block scalars, quoting, key order, and indentation width all survive byte for byte. Rewriting `.github/` references to `.depot/` is unchanged in behavior but moves to fit. It was a walk over the node tree, which would have forced the re-encoding fallback for any workflow carrying a local action reference — nearly all of them — and so would have given up the fidelity above in exactly the common case. It is the same pure text substitution that RewriteGitHubPathsInDir already applies to copied action files, so it now runs over the source ahead of the parse. The text that gets parsed is the rewritten text, no edit offset has to be adjusted for it, and a workflow whose paths are rewritten keeps its formatting. Two details make the in-place path safe rather than merely plausible. Every extracted scalar token is re-parsed and compared against the value the node reported, so a mis-delimited extent fails closed. And when any single change cannot be expressed as an edit — an anchored label, a flow mapping of triggers — the whole file falls back to re-encoding, which reformats but is never wrong. Correctness first; fidelity where the shape of the file allows it. This also removes a latent bug: the pass that comments out uncorrectable jobs searched for a job key at exactly two spaces of indent, which only held because the encoder had already normalized the file it was reading. It now uses each job's own indentation. Refs DEP-5306. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
robstolarz
force-pushed
the
rob/dep-5306-migrate-yaml-fidelity
branch
from
August 14, 2026 23:27
79930a4 to
4dbd2a6
Compare
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.



Closes DEP-5306. Based on
main— this was previously stacked on #554, and since that PR was closed (the.github/→.depot/rewrite stays) the branch has been rebased--onto mainto drop that commit. The rewrite's behavior is unchanged by this PR.depot ci migrateparsed each workflow with yaml.v3 and re-encoded the node tree. That reformats the whole file, so a user reviewing the migration diff sees the encoder's opinions mixed in with the actual changes:run: |blocks collapse. yaml.v3 will not emit a literal block whose lines end in whitespace — one stray trailing space anywhere in a script and the whole block comes back as a single\n-escaped double-quoted string. Shell that was readable is now not.Approach
Parse to locate, not to rewrite. The node tree is now used only for the line and column of the things migration actually changes; each change is spliced into the original bytes as a text edit. What isn't edited isn't touched — blank lines, block scalars, quoting, key order, and indentation width all survive byte for byte, and a workflow that needs no changes comes back byte-identical under its header.
Two things make that safe rather than merely plausible:
Also fixes a latent bug this surfaced: the pass that comments out uncorrectable jobs searched for a job key at exactly two spaces of indent. That only ever held because the encoder had already normalized the file it was reading — on the original text, a workflow indented with four spaces would have had its disabled job silently left live. It now uses each job's own indentation.
The one place the path rewrite had to move
The diff deletes
transformGitHubPaths, and that is not the removal #554 proposed — the rewriting still happens, with the same guards and the sameChangePathRewrittenrecord. It changed where it runs.It was a walk over the node tree, mutating scalar values. Under in-place editing that would have forced the re-encoding fallback for any workflow carrying a local
.github/reference — nearly all of them — so the fidelity fix above would have applied to almost nothing. It is the same pure text substitutionRewriteGitHubPathsInDiralready applies to copied action files, so it now runs over the source ahead of the parse. The parsed text is the rewritten text, so no edit offset needs adjusting, and a workflow whose paths are rewritten keeps its formatting.transform_test.go's existing path-rewrite assertions are unmodified and pass, which is the check that behavior didn't drift.A note could be broken out of (found in review)
Cursor's security reviewer flagged the generated
# was: <label>note as unsanitized, and it was right. Any label that isn't a Depot label, a standard GitHub runner, or an expression is remapped as "nonstandard" — so the quoted text is arbitrary workflow content, not one of a known set. A comment ends at its line's end, soruns-on: "self-hosted\ninjected: pwned"ended the note and left the rest of it standing as a live YAML line in the migrated workflow.The re-parse guard does not catch this: the result is still valid YAML, just no longer the author's workflow. Notes are now flattened before splicing — content kept, line breaks become spaces. Both a unit test on the splice and an end-to-end test asserting the migrated document has exactly its original three keys; the unit test fails without the fix.
Tests
The fidelity tests are written as byte-exact comparisons against the input rather than
Containschecks, which is the only way to catch an unintended reformat.run: |block with a trailing space, four-space indentation, quoting, and comments all survive aruns-onremap.on: {}— none of them disturb surrounding blank lines or leave a double gap.on:keeps a trailing comment on its own line; an author's existing line comment is never overwritten (the note goes above instead).run: |block comes back with only its two local paths rewritten, byte-exact otherwise, with a remoteorg/repo/.github/actions/...reference untouched. Run againstmainthis fails on all three counts (blank lines dropped, block collapsed to"…build.sh \n\nmake test\n", indentation normalized) — it is the fail-first proof for the combined behavior.Shapes that route to the re-encode fallback
Anchored or aliased scalars; block or folded scalars where one is being replaced; multi-line plain scalars; a flow mapping of triggers containing an unsupported one; an entry that doesn't begin its own line; any scalar whose extracted token doesn't re-parse to the node's value; overlapping planned edits; a spliced result that doesn't parse.
All of these produce today's output, so this change is never-worse-than-before rather than best-effort.
Why the other two approaches were rejected
Sentinel comments (insert a marker comment per blank line, strip after encoding): yaml.v3 reflows comments to its own idea of placement, and a sentinel can't represent a blank line inside a block scalar — which is where they matter most in a
run:script.Trimming trailing whitespace from
run:blocks so the encoder keeps the block style: this fixes the symptom by editing the user's shell text, and does nothing for blank lines outside block scalars.Deferred / known-open
Shipping with these documented rather than fixed, so the reported problem lands now.
.github/→.depot/rewrite goes wrong. They would have been resolved by construction had ci migrate: stop rewriting .github/ references to .depot/ #554 landed; with the rewrite retained they remain open, and they are ci migrate: resolve referenced paths and preserve YAML readability #533's territory. This PR covers item 4 (therun:block collapse) and the blank-line report..github/on the raw source text. In"\t.github/actions/x"that character is the literalt, not a tab, so the reference is left alone where the node-tree pass would have rewritten it. It fails closed (path untouched, never mangled), and needs a contrived fixture to hit.TestRunShellCommandCancellationAllowsTermCleanupinpkg/cmd/testsfails intermittently (1/5 on this branch, 1/6 on a cleanorigin/mainworktree). This stack touches no file in that package.History
The review-fix and narration-cleanup commits are folded into this feature commit. The final tree is unchanged; no behavior or rollout change is included. The consolidated history was verified with per-commit Go builds,
go vet ./..., andgo test ./....Note
Medium Risk
Changes how migrated workflows are produced (correctness and diff fidelity); fallback limits blast radius, but wrong in-place edits could still break customer CI YAML.
Overview
depot ci migrateno longer re-encodes whole workflow files through yaml.v3 for the common case. It parses YAML only to find line/column positions, then splices text edits into the original bytes so blank lines,run: |blocks, quoting, and indentation stay byte-identical except where migration actually changes something.TransformWorkflownow runs the existing.github/→.depot/path rewrite on raw text before parse (same rules as before), then triestransformInPlacefor trigger removal,runs-onremapping with notes, and commenting out uncorrectable jobs at each job’s real indent. If any edit is unsafe (anchors, bad scalar extents, flow trigger maps, etc.) or the result fails to parse, it falls back to the previous re-encode path.Adds
textedithelpers (line indexing, scalar extent with re-parse checks,applyEdits) andcommentSafeso generated# was:notes cannot inject newlines into YAML. Large byte-exact fidelity tests cover triggers, CRLF, path rewrite + formatting, and the re-encode fallback.Reviewed by Cursor Bugbot for commit 4dbd2a6. Bugbot is set up for automated code reviews on this repo. Configure here.