Update Hooks and Side Effects: Keep a Retried Install from Repeating Irreversible Work

Make each hook effect repeat-safe or give it a stable deduplication identity and a reconciliation path. A local “done” file cannot, by itself, make an external action happen exactly once across power loss. If an interruption leaves the action’s outcome unknown, stop and resolve that uncertainty instead of blindly repeating irreversible work.

This guide covers bundle-defined RAUC 1.14 slot hooks. It does not provide production scripts or change external services. The ledger and test matrix are original design proposals; no hook, installation or external action was executed. System handlers and full custom update handlers are separate extension mechanisms with different contracts.

1. Record where each hook can run

The 1.14 slot-hook guide places pre-install before the slot update and post-install after successful slot updating. A slot-install hook replaces the default slot installation and excludes those pre/post hooks. Bundle hooks share an executable that selects behavior from its invocation argument.

Inventory each enabled invocation by slot class, target scope and effect. Do not assume it runs once per bundle: slot-specific work can run for multiple images. Nor should a design depend on every retry invoking every hook, because actual target selection, image handling and skip behavior matter.

Separate checks from changes. A compatibility or prerequisite check should be read-only where practical. Work that contacts an external system, modifies shared persistent state or affects another controller deserves an explicit effect contract before it is placed in the installer path.

A stable logical-effect key and durable ledger surround effect dispatch; missing replies or receipts trigger keyed reconciliation or an unresolved hold before completion is recorded.
Original hook side-effect model. A local ledger cannot make an unsupported external action exactly once across power loss.

2. Classify effects by replay behavior

Effect class Example design Replay requirement
Pure validation Compare supported public identifiers Same input produces a consistent decision without changing state
Desired-state assignment Ensure an owned record equals an approved value Repeat converges to the same validated state
Append or increment Add one maintenance event to a ledger Stable effect key and duplicate suppression
External irreversible action One-shot operation owned by another component Supported idempotency/query contract, or explicit reconciliation hold

A command returning success twice does not prove idempotency. An increment can succeed twice while violating the intended effect. Prefer assignments with explicit preconditions over relative changes when the target API supports them, and verify the resulting state rather than only a process exit code.

3. Use a logical-effect identity

Define a key from the effect schema/version, approved artifact or transition identity, and target scope. The key must remain the same when retrying the same intended effect. A new installation attempt identifier alone may change on retry and therefore defeat duplicate suppression.

The slot-hook interface documents slot identity and optional image metadata, including image digest. Validate required fields before use. Do not assume every variable listed for system handlers is also a guaranteed slot-hook input, or treat a filename as authenticated effect identity.

Decide whether the effect is once per physical device, slot, release, or approved transition. A per-slot marker may legitimately allow two executions when the requirement was once per device. Conversely, a device-wide marker can wrongly suppress required work when a later transition intentionally repeats an operation.

4. Design for the action-to-receipt gap

A useful local ledger has PLANNED, IN-FLIGHT, DONE and UNRESOLVED states, with durable records appropriate to its storage backend. Before acting, inspect any existing record and the target’s current state. Persist the intended effect before dispatch and store a verified result afterward.

There is still a gap: the external target can complete an action just before the device loses power, leaving no local completion record. Moving the “done” write earlier merely creates the opposite failure, where the record says complete but the action never happened.

Interruption point Safe replay decision
Before dispatch, definitely unsent Resume only after revalidating the current approved intent
After dispatch, before authoritative reply Query by the stable key or enter UNRESOLVED
Target completed, local receipt missing Recover the target’s result without repeating the effect
Durable DONE with matching identity Return the recorded result after any required consistency check
Same key with conflicting inputs Reject the conflict; do not silently reuse the old receipt

If the external system supports neither idempotent keyed execution nor an authoritative query, the product cannot promise automatic exactly-once recovery from that ambiguous state. Move the action outside the retryable installer path or require an approved operator resolution.

5. Keep hook failure separate from undo

In v1.14 update_handler.c, the raw-image handler invokes its post-install hook after image writing, and hook failure propagates as failure. That return path does not reverse the already-written image or undo effects performed by the hook.

Therefore a failed installation report must retain which effects may already have occurred. A later retry should reconcile each logical effect independently rather than treating the whole attempt as if nothing happened. A compensation action, if supported, needs its own identity, authority and qualification; it is not an automatic inverse of every side effect.

Store the ledger where the relevant retry and reboot paths can recover it. A marker written inside a slot that the next attempt overwrites is not a durable cross-attempt record. Preserve required user data and avoid expanding permissions simply to make the ledger writable.

6. Test with harmless effect surrogates

Begin with a fixture that records logical-effect keys and simulates target replies without changing hardware or production services. Exercise duplicate calls, two slots, a changed attempt identity, conflicting input under one key, and a lost reply after simulated target completion.

Then qualify real persistence and hook invocation boundaries on recoverable development hardware. Interrupt before dispatch, after target completion and before the local receipt becomes durable. Verify that ambiguous cases remain unresolved and that completion records survive the intended retries.

These tests are proposed, not executed. A host fixture can validate state transitions but cannot prove the storage stack’s durability or another system’s deduplication guarantees.

Hypothetical duplicated maintenance event

A post-install hook asks a service to record one maintenance event. The service accepts it, but the response is lost and installation reports failure. Retrying with a fresh attempt UUID would create a second event if that UUID were the deduplication key.

With a stable logical-effect key, the service instead returns the existing result, which the device records durably. If the service cannot establish that result, the hook reports an unresolved effect. That honest state is safer than declaring exactly-once behavior based on a local marker alone.

Similar Posts