Skip to content

feat(core): let an orchestrated migrate run give up on migrations, adopt hand-applied ones, and cap retries - #36986

Draft
leosvelperez wants to merge 12 commits into
nxc-4628from
nxc-4819
Draft

feat(core): let an orchestrated migrate run give up on migrations, adopt hand-applied ones, and cap retries#36986
leosvelperez wants to merge 12 commits into
nxc-4628from
nxc-4819

Conversation

@leosvelperez

@leosvelperez leosvelperez commented Sep 10, 2026

Copy link
Copy Markdown
Member

Current Behavior

This only applies to orchestrated migrate runs, behind NX_MIGRATE_ORCHESTRATOR.

When a migration fails, nx offers the agent three choices: retry, retry over a clean tree, or skip. A dead worker gets a fourth choice, adopt, which keeps the working tree as the result. Nothing lets the agent say "this migration is not going to work, move on". Nothing lets it keep a fix it applied by hand after a plain failure. A skip pretends the migration did not apply to the workspace, which is wrong for both cases.

The agent can retry a failing migration forever. Each retry costs time and the failure is usually the same.

At the end, the run reports success even when a migration never landed. The user has no list of what was given up, and a session that nx started for the run exits 0.

Expected Behavior

After a failure or death the menu gains:

  • adopt after a plain failure: the tree as left is the result, committed under the migration's name.
  • unresolved: the agent gives the migration up and nx files the failure as a run issue. Safely discardable changes are reset; others stay and, with commits on, land under the migration's name marked unresolved.

A migration with a commit on record gets adopt instead of skip and unresolved, committing what changed since. Nx recovers a commit the session landed for a worker that died before recording it. A commit made in-process, or one whose session ended first, is not detected. A retry resumes the remaining work without reapplying the generator.

nx refuses third retries.

The completion report counts applied, adopted, skipped and unresolved migrations and lists each given-up one's failure. A session nx started exits 1 on a given-up migration or open issue.

Related Issue(s)

NXC-4819

Implementation Notes

  • unresolved is a terminal step status, so a resumed run never re-dispenses a given-up migration.
  • A give-up demands a reset only with commits on, a tree that was clean at hand-out, and no later commit at risk. Until the tree is clean, nx refuses it. Anything else is kept, because dropping hand-made edits is worse than an unresolved commit.
  • The parent session lands the unresolved and adopted commits through the same broker as normal commits. Their request ids carry the action, so neither a replay of the worker's earlier failed request nor the cached answer to a commit that already landed is mistaken for them.
  • The retry cap is two rearms per migration, enforced when the action is applied, so a hand-typed retry past the cap is refused too.
  • Each landed ledger entry records the attempt that landed it. The recovery uses that to tell an answer it already recorded from a new one, even when the commit's sha did not resolve.
  • The run-state format stays at v1. The orchestrator is dark until NXC-4817 and runs written under the flag are not supported across Nx versions, so closed-set additions do not bump the format until promotion.

@nx-cloud

nx-cloud Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 450f46e

Command Status Duration Result
nx affected --targets=lint,oxlint,test,build,e2... ✅ Succeeded 6m 12s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 44s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 15s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-09-10 20:51:06 UTC

@leosvelperez leosvelperez self-assigned this Sep 10, 2026
@leosvelperez leosvelperez changed the title nxc 4819 feat(core): let an orchestrated migrate run give up on migrations, adopt hand-applied ones, and cap retries Sep 10, 2026
@leosvelperez
leosvelperez added this pull request to stack #36919 September 10, 2026 08:11
@netlify

netlify Bot commented Sep 10, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 685eeb7
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6aa315bd5fdbe900087a51ed
😎 Deploy Preview https://deploy-preview-36986--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Sep 10, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 685eeb7
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6aa315bd9f79040008b518aa
😎 Deploy Preview https://deploy-preview-36986--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

…ed step

A failed or died step could only be retried, skipped, or (from died)
adopted, so a migration nobody could fix had to be skipped, which reads as
"not applicable" in the run's record. The new 'unresolved' status is
terminal and keeps the attempt count and the last failure on the step;
'--step-action=unresolved' reaches it from failed or died. The tree is
handled like a skip: the install the dead worker owed runs, and on a
committing run the dirty tree is recorded as commit debt. Adopted steps
carry an 'adopted' marker so the completion report can list them without
parsing summaries.

No run-state format bump: the orchestrator is dark behind
NX_MIGRATE_ORCHESTRATOR.
…p is given up on

Giving up on a step left its tree unhandled beyond the install a skip owes.
Now the reconcile decides what happens to it. When the generator never
completed and the same restore point a clean retry needs exists, the agent
resets the tree and nx accepts only a verifiably clean one, reopening the
issue resolutions the reset discards. Otherwise a committing run commits
the partial result under the migration's name with an "(unresolved)"
marker, through the parent session's broker when there is one, so a later
revert does not have to untangle it from the next step's work; a run
without commits keeps the tree as a skip does.

The step's transition, a run issue carrying its last failure to the
completion report, and the step's reference to that issue land in one
locked write. The issue is unscoped and deferred so no later step claims
it; an agent report with the same text is taken over rather than merged.
A lost archive file warns and never undoes the transition.

The broker names a marked commit as its own request, so giving up after a
worker's commit failed at its install lands a commit instead of replaying
that failure.
…ed by hand

A migration whose generator failed and was then applied by hand had no
way into the run's record except a retry that reran the generator. The
adopt action now accepts a failed step as it does a died one: the tree is
taken as the migration's result, the step succeeds with the adopted
marker, and a committing run commits it under the migration's name.

Through the parent session's broker a failed step's adopt is its own
request. The worker's own commit request for that attempt ended in the
failed install the step recorded, so replaying that answer, as a died
step's adopt does on purpose, would skip the install and the commit.
…earms

The rearm cap was advisory: past three rearms the failed or died dispense
argued against retrying and withheld the preselected continuation, but an
explicit retry was still accepted, so a step could be retried forever.
The cap is now two rearms (three attempts) and it refuses: the reconcile
rejects retry and retry-clean past it, naming the cap, and the dispense
lists only adopt, skip and unresolved.
…d migrate run leaves work unresolved

The completion report counted applied and skipped steps and the master
session exited 0 for any completed run, so a migration given up on and a
problem nobody fixed both read as success. One tally now classifies every
step: applied, adopted, skipped and unresolved, with each given-up
migration listed with the failure it was given up on. The orchestrator's
completion block, the resume banner and the master session's report share
it, and the master session exits 1 when a step is unresolved or a reported
issue is still unresolved. The run itself stays completed.

Failure text is agent or generator output printed verbatim, so the report
collapses it to one line: a break inside it could open a forged block.
…and the retry budget

The runbook described a failed or died step as a choice between retry
and skip, and the dispense said nothing about how many retries a step
had. The agent now gets the decision rule: diagnose the failure first,
retry only with a plausible fix in hand, two retries per step with the
user asked before the last one, adopt for a result already in the tree,
skip for a migration that does not apply, and unresolved to give the
migration up. With nobody to answer, it gives the step up and continues;
an unfinishable prompt reaches that option by writing the failed handoff
itself. Each failed and died dispense opens with the retries left.

The exit code is promised only for a session nx started itself; an agent
driving the loop reads the completion block's unresolved tally instead.
…grate orchestrator e2e

Five orchestrator cases: give up on a killed worker by reset and check
the minted issue, commit the partial tree of a given-up prompt step under
its name marked unresolved, adopt a failed prompt step applied by hand,
refuse a retry past two rearms, and a master session that exits 1 with
the given-up migration in its report. The fake agent gives a step up
through the dispense's unresolved option only when
FAKE_AGENT_FAIL_PROMPTS makes its prompt-based migrations fail; generator
validation handoffs still succeed and retry-failed still retries
otherwise.
…adopted, skipped or given up

A worker commits its migration before it reports the outcome, so a step
can fail or die with its commit already in the ledger. Adopting such a
died step re-published the worker's own commit request; under the session
broker the cached answer came back and the ledger entry landed twice, and
in-process the tree was committed again under the worker's name. The
failed and died dispenses also offered skip and unresolved for the step,
and acceptance took them, so the run could record a migration whose result
sits in history as skipped or given up.

Adopt now asks under its own commit-adopt request whenever the step is
failed or its commit already landed, so a cached worker answer is never
replayed and repairs made since land as their own commit. Skip and
unresolved are withheld from both dispenses and refused at acceptance,
naming the landed sha and pointing to adopt; the retry budget, cap and
retry-clean fallback lines, the runbook and the multi-repo migrate skill
say the same. The persisted failed-adopt summary no longer claims the tree
was applied by hand.

The overlong-id digest test gives both steps the same attempt and failure
so only the digest tells the summaries apart; the broker seam comment is
scoped to a failure with no landed entry.
…ted migrate step is given up

A died step carries no outcome (markDied records the status only and a
re-arm rebuilds the step without one), so giving it up reached the
completion report and the minted issue with "no failure detail was
recorded". The died -> unresolved transition now records the death as
the step's failure, and the issue is minted from the transitioned step
instead of the pre-transition snapshot.

Also: the mintUnresolvedIssue doc no longer claims the migration id keeps
two steps from folding, the archive-shape comment sits above
newIssueArchiveRecord again, and the commitForStep contract names failed
adoption.
… out of skip and unresolved

The gate that withholds and refuses skip and unresolved for a landed
commit reused the reset-hazard check, which ignores an earlier attempt's
commit once it is an ancestor of the re-captured ref and cannot see a
commit the session landed for the current attempt when the worker died
between the broker's answer and its ledger append. A retried step that
failed again, or a worker killed in that window, could be recorded
skipped or unresolved with its commit in history.

The gate, both dispenses and the adopt request choice now use the last
ledger entry covering the step, whichever attempt landed it. Reconcile
recovers a dead worker's brokered commit from the session's cached
answer before validating any step action, and appends it once: landed
entries record the attempt that landed them (ownerAttempt), with the sha
as the key for entries written before the field existed. A commit a
worker made in-process and died before recording leaves no answer and is
not detected; the runbook, the repo skill and the PR body say the
options are withheld for a commit the run has on record.
…ases in migrate orchestrator specs

The closed-set JSDoc on NewerRunStateFormatError demanded a format bump
for every new member, but `unresolved` joined the step statuses at v1 on
purpose: runs are not supported across Nx versions while the orchestrator
is dark, and the bump waits for its promotion. The `adopted` field comment
excluded a dead worker's landed commit, which adopt also accepts.

Specs gain the `recorded` disposition in the master-session exit table and
a died step with no cached broker answer, which keeps skip and unresolved
open and records no entry.
…ases in migrate orchestrator specs [Self-Healing CI Rerun]

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.


🎓 Learn more about Self-Healing CI on nx.dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant