Skip to content

Gate set_last_save_timestamp on save script exit code (fixes #162)#163

Open
pleasedodisturb wants to merge 1 commit into
tmux-plugins:masterfrom
pleasedodisturb:fix/silent-save-timestamp
Open

Gate set_last_save_timestamp on save script exit code (fixes #162)#163
pleasedodisturb wants to merge 1 commit into
tmux-plugins:masterfrom
pleasedodisturb:fix/silent-save-timestamp

Conversation

@pleasedodisturb

Copy link
Copy Markdown

Fixes #162.

Summary

fetch_and_run_tmux_resurrect_save_script previously backgrounded the save with & and called set_last_save_timestamp unconditionally, so silent save failures (wrong path, missing file, non-zero exit, error swallowed by >/dev/null 2>&1) still advanced @continuum-save-last-timestamp. Result: tmux_resurrect_*.txt files stop being written to disk while continuum keeps reporting healthy saves indefinitely.

This is the issue tracked in #162, with deeper symptoms in #22 (open since 2016) and #94. Independent of #159 (boot-grace race in the same file) but both fixes can land.

Change

 fetch_and_run_tmux_resurrect_save_script() {
     local resurrect_save_script_path="$(get_tmux_option "$resurrect_save_path_option" "")"
     if [ -n "$resurrect_save_script_path" ]; then
-        "$resurrect_save_script_path" "quiet" >/dev/null 2>&1 &
-        set_last_save_timestamp
+        if "$resurrect_save_script_path" "quiet" >/dev/null 2>&1; then
+            set_last_save_timestamp
+        fi
     fi
 }

Two changes:

  1. Drop & — run the save script synchronously so the caller sees the exit code.
  2. Gate set_last_save_timestamp on success. On failure the timestamp stays put and the next tick (every status interval, gated to every save-interval minutes) will retry.

Behavioural impact

Aspect Before After
save script success timestamp set timestamp set
save script exits non-zero timestamp set (bug) timestamp NOT set
save script missing timestamp set (bug) timestamp NOT set
status redraw latency save runs in background (returns instantly) one save-script duration added to one redraw per save-interval

In practice the synchronous save adds ~80ms with @resurrect-capture-pane-contents 'on' and 10 panes — invisible at that scale, and only once every save-interval (default 15 min). Users with extremely large pane scrollback may see a longer pause; this is the cost of correctness.

Side benefit: the existing acquire_lock trap now correctly holds the lock for the duration of the save. Previously the trap fired on wrapper exit while the backgrounded save was still running, making the lock effectively a no-op for save serialization.

Verification

Targeted test at the function level (sources the function with stubbed get_tmux_option / set_last_save_timestamp, exercises each branch). Same harness run before and after to demonstrate the bug and the fix:

# Case Master @ 0698e8f This PR
1 probe exits 0 timestamp set ✓ timestamp set ✓
2 probe exits 1 timestamp set ✗ timestamp NOT set ✓
3 save script path missing timestamp set ✗ timestamp NOT set ✓
4 @resurrect-save-script-path unset no-op ✓ no-op ✓

(✗ = bug present; ✓ = correct behaviour.)

Workaround pointer

For users hitting this in the wild who want a fix today without waiting on this PR, a wrapper script and a real-mtime status-bar widget are at https://github.com/pleasedodisturb/terminal-craft/pull/43 (scripts/continuum-save-guard.sh + scripts/save-status.sh). The widget reads file mtime directly instead of trusting @continuum-save-last-timestamp — the lying timestamp this PR fixes.

Test plan

  • Bug reproduced against master @ 0698e8f (cases 2 + 3 show timestamp advancing despite failure)
  • Bug fixed by this PR (same cases now correctly leave the timestamp put)
  • Success path (case 1) and unset path (case 4) unchanged
  • Tab indentation preserved (matches surrounding code style)

`fetch_and_run_tmux_resurrect_save_script` previously backgrounded the
save script with `&` and called `set_last_save_timestamp`
unconditionally. When the configured save script silently failed (wrong
path, missing file, exec error swallowed by `>/dev/null 2>&1`), the
last-save timestamp still advanced — making
`@continuum-save-last-timestamp` appear healthy while nothing was being
written to disk. The same anti-pattern as a backup system without
restore drills: the system actively lies about its health, the user has
no signal, and saves are silently lost across reboots.

This is the root cause behind reports like:
- tmux-plugins#22 — "automatic saving not working when tmux status is off"
- tmux-plugins#94 — saved files contain incomplete session data
- numerous downstream wrappers users have written to compensate

Change: run the save script synchronously and only call
`set_last_save_timestamp` when it returns exit 0. On failure the
timestamp stays put, so the next save tick (5s status interval, gated
to every save-interval minutes) will retry instead of skipping ahead.

Behavioural impact:
- Synchronous save adds at most one save-script duration to one status
  refresh per save-interval (default 15 min). Tested locally with
  `@resurrect-capture-pane-contents 'on'` on 10 panes: ~80ms. Status
  bar pause is invisible at that scale.
- The existing `acquire_lock` trap now correctly holds the lock for the
  duration of the save (previously the trap fired on the wrapper exit
  while the backgrounded save was still running — the lock was
  effectively a no-op for save serialization).

Verified with a probe save script in 4 cases (test scaffolding at
`fetch_and_run_tmux_resurrect_save_script` level, with `get_tmux_option`
and `set_last_save_timestamp` stubbed):

| Case                       | Before | After  |
|----------------------------|--------|--------|
| probe exits 0              | set    | set ✓  |
| probe exits 1              | set ✗  | NOT ✓  |
| save script path missing   | set ✗  | NOT ✓  |
| @resurrect-save-script-path empty | NOT  | NOT ✓  |

Related: tmux-plugins#159 (boot-grace race that clobbers the `last` symlink) is a
separate but adjacent bug in the same file — both stem from
assumptions about backgrounded operations completing successfully.
This patch is intentionally minimal and does not touch tmux-plugins#159's scope.

Reviewed-by: claude-opus-4-7 (code+security)
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.

set_last_save_timestamp advances unconditionally — auto-save reports healthy while silently failing

1 participant