Commit fe3a41f
authored
fix(workflow-diff): roll back to prior state when n8n PUT fails post-save (czlonkowski#769)
* fix(workflow-diff): roll back to prior state when n8n PUT fails post-save
When n8n_update_partial_workflow's underlying PUT fails (e.g. n8n persisted
the workflow body but the activation step within the same PUT then tripped
on an unsupported typeVersion), the workflow was being left in a broken
state. The handler already snapshots the prior workflow into workflowBefore
for telemetry, so we piggyback on it: on PUT failure, attempt a rollback
PUT to restore the prior state.
Three return paths on failure:
- Pre-mutation (no snapshot, e.g. validateOnly) — preserve current behaviour
- Rollback succeeded — error suffixed with "(workflow restored to prior
state)" + details.rollbackPerformed = true
- Rollback failed — composite error pointing to n8n_workflow_versions for
manual recovery + details.rollbackPerformed = false + rollbackError
Success path is byte-identical to before.
Tests:
- New "should roll back to prior state when n8n PUT fails after persisting
body" test asserts the rollback PUT fires with the prior snapshot
- New "should report rollback failure when both PUTs fail" test asserts
the composite error is surfaced
- Existing "should not attempt rollback in validateOnly mode" remains intact
Known limitations (documented in PR description):
- Concurrent writers can race the rollback. Same race window as today's
plain PUT. A future iteration could use versionId for optimistic
concurrency.
- Rollback fires even when n8n rejected pre-save (body never persisted).
Rollback is a no-op in that case — one extra HTTP request on the
failure path. Chose this over guessing n8n's error semantics.
* fix(workflow-diff): skip rollback on pre-save rejection + actionable recovery
Building on the rollback-on-error fix, distinguish two failure modes via a
post-failure GET that compares versionId / versionCounter / updatedAt
against the snapshot:
- Persist-then-fail (n8n saved the body before activation died): roll back
by re-PUTting the prior snapshot. Error gets the existing
"(workflow restored to prior state)" suffix.
- Pre-save rejection (body never persisted): skip the rollback PUT — it
would be wasted and the suffix would mislead the caller.
details.rollbackPerformed: false; no suffix.
When rollback fails, surface workflowBefore.versionId as
details.priorVersionId so callers can recover via n8n_workflow_versions.
The version comparison is tri-state (same / changed / unknown). On
"unknown" — older n8n versions that omit all three fields, or a failing
post-failure GET — fall back to attempting rollback. The silent-corruption
class from czlonkowski#770 is far worse than a redundant PUT.
Tests cover persist-then-fail rollback, pre-save no-rollback, GET-failure
best-effort rollback, versionCounter-only fallback, no-version-fields
safety net, and double-PUT-failure with priorVersionId. 45/45 in
handlers-workflow-diff.test.ts; 611/611 across tests/unit/mcp.
Bumped to v2.50.3.1 parent bcaba83 commit fe3a41f
7 files changed
Lines changed: 426 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
10 | 20 | | |
11 | 21 | | |
12 | 22 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
24 | 46 | | |
25 | 47 | | |
26 | 48 | | |
| |||
325 | 347 | | |
326 | 348 | | |
327 | 349 | | |
328 | | - | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
329 | 444 | | |
330 | 445 | | |
331 | 446 | | |
| |||
0 commit comments