Skip to content

fix(restore): treat a 404 restore-result ack as terminal so cross-greffer migration leftovers self-clean#117

Open
omarsy wants to merge 1 commit into
mainfrom
fix/restore-callback-404-terminal
Open

fix(restore): treat a 404 restore-result ack as terminal so cross-greffer migration leftovers self-clean#117
omarsy wants to merge 1 commit into
mainfrom
fix/restore-callback-404-terminal

Conversation

@omarsy

@omarsy omarsy commented Jun 24, 2026

Copy link
Copy Markdown
Member

Problem

A cross-greffer migration leaves one orphan restore-state file (.restore_<id>.json) per migration on the target greffer (G2).

The migration cutover drives a restore on G2 with no manager RestoreRun (by design: the manager polls G2's durable restore-status instead, see manager migration_services._cold_restore_and_wait). So when G2 posts its restore-result callback, the manager has no row to finalize and never returns 2xx. The greffer only deleted its on-disk restore-state file after a 2xx ack, so the file lingered forever. Impact is low and bounded (one harmless leftover per migration; a benign 404 logged on each G2 boot reconcile), but it is a wart worth removing.

Fix

_post_callback now returns a 3-state outcome instead of a bool, and a new _callback_settled predicate decides when the durable state file may be dropped:

Manager response Outcome State file
2xx _CB_ACKED (finalized) dropped
404 (no RestoreRun / instance gone) _CB_GONE (terminal) dropped
403 / 5xx / other / network _CB_RETRY (transient) kept for boot reconciliation

Why 404 is terminal but 403 is not (the load-bearing invariant)

During the live migration restore the instance FK is still on the source greffer, so G2's in-flight callback 403s (token/ownership mismatch) and the manager is actively polling G2's restore-status through that exact window. Dropping the file on a 403 would strand that poll and time the migration out. A migration's 404 only arrives after cutover (FK flipped to G2, still no RestoreRun), observed on a later G2 boot reconcile, when the manager no longer needs the file.

A normal (non-migration) restore cannot get a spurious 404 either: the manager creates the RestoreRun before dispatching to the greffer, and a replay on a terminal row is an idempotent 200 no-op, so a 404 there means the run/instance is genuinely gone.

This was verified against the manager source (instance_restore_result -> _callback_instance_or_403 returns 403 on token mismatch / 404 on missing instance; finalize_restore returns None -> 404 only when no matching RestoreRun).

Tests

  • test_post_callback_classifies_status: 200/204 -> ACKED, 404 -> GONE, 403/400/500/network -> RETRY.
  • test_callback_settled_only_acked_or_gone: the settle predicate.
  • test_reconcile_drops_restore_state_on_404_gone: the orphan self-cleans on 404.
  • test_reconcile_keeps_restore_state_on_403_retry: regression guard that a 403 keeps the file (protects the migration poll dependency).

Full greffer suite green (687 passed).

Scope

Greffer-only. No manager change required. Surfaced while wiring the manager's reap_stuck_ops command (feat/reap-stuck-ops); unrelated to it and intentionally split into its own PR.

… leftovers self-clean

A cross-greffer migration drives a restore on the target greffer (G2) with NO
manager RestoreRun (the manager polls G2's durable restore-status instead). G2's
restore-result callback therefore never gets a 2xx, and because the greffer only
deleted its on-disk restore-state file after a 2xx ack, that .restore_<id>.json
lingered forever (one orphan per migration; a benign 404 logged on each G2 boot
reconcile).

_post_callback now returns a 3-state outcome instead of a bool:
  - _CB_ACKED (2xx): the op is finalized.
  - _CB_GONE (404): the manager has no record of the run (or the instance is
    gone). Terminal, so the durable state file is safe to drop.
  - _CB_RETRY (5xx / 403 / other / network): transient, keep the file for boot
    reconciliation.

403 is deliberately RETRY, not terminal: during the LIVE migration window the
instance FK is still on the source greffer, so G2's in-flight restore callback
403s while the manager is actively polling G2's restore-status through exactly
that window. Dropping the state file on 403 would strand that poll and time the
migration out. A migration's 404 only arrives AFTER cutover, when the manager no
longer needs the file. A normal restore can't get a spurious 404 either: the
manager creates the RestoreRun before dispatching, and a replay on a terminal
row is a 200 no-op, so 404 there means the run/instance is genuinely gone.

Tests: classify every status (200/204 -> ACKED, 404 -> GONE, 403/400/5xx/network
-> RETRY), the settle predicate, and reconcile_on_boot dropping on 404 while
KEEPING on 403 (regression guard for the migration poll dependency).
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

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