Skip to content

fix(bin): key declared-wait resurface cadence and age on the declaration, not the status file - #4742

Open
mremond wants to merge 3 commits into
kunchenguid:mainfrom
mremond:fm/fm-status-write-resets-pause-cadence
Open

mremond wants to merge 3 commits into
kunchenguid:mainfrom
mremond:fm/fm-status-write-resets-pause-cadence

Conversation

@mremond

@mremond mremond commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Writing a status line that does not change a declared wait must not reset its resurface cadence.

The defect: writing a status line on a task with a declared wait (paused: or captain-held:) cancels the long resurface cadence and wakes the supervisor immediately, even when the line does not change what is awaited.
Measured on 2026-09-14 on two different tasks: a note appended to a paused task's status log produced a signal: wake for the write, then a stale: wake reading "paused 91s" (and "paused 153s" on the second task, predicted in advance), exactly the seconds elapsed since the write; before the write the same pane read "paused 14776s", matching the expected 14400s cadence.
A third occurrence on 2026-09-15 re-asked for confirmation 86 seconds after the confirmation line was written, a factor of 167 below the cadence.
The corroborating observation on the captain-held: branch: two workers produced a stale notice within a minute of writing their captain-held: declaration.

The cause, read in bin/fm-watch.sh: handle_paused_stale() builds its declaration scope as declared:$(fm_wake_signal_sig "$statusf"), a file signature that includes size, identity and mtime (status_observed_signature() in bin/fm-classify-lib.sh), and passes it to resurface_absorbed(); when the scope differs from the throttle marker's content, both the age gate and the cadence gate are skipped and the wake is appended immediately.
The age shown is also anchored on the status file's mtime, so any write resets it.
The comment above that code says the throttle bounds the DECLARATION rather than the pane hash; the code keys it on the status file instead.

What is wanted:

  1. Key the declared-wait resurface scope on the declaration content (the wait line itself - verb and reason), not on the status file signature, so a write that does not change what is awaited leaves the cadence intact, while a genuinely new declaration still surfaces immediately.
  2. Decide the age anchor on the same basis: a write that does not change the wait should not reset the age.
  3. Tests for both paused: and captain-held: declarations, each with the no-change write (no wake before the cadence) and the real change (immediate wake).
  4. The delivery must say which residue class of unwanted stale wakes it closes and which remains; do not promise silence. Stale wakes arriving as "possible wedge, escalation 1" with a live run step (observed 2026-09-09/10) go through a different path, were not measured, and are not claimed by this fix.

Relation to upstream PR #3605: 3605 treats the symptom by moving the age gate out of the scope-matching if, so a replacement declared wait is absorbed while young.
The upstream maintainer classed 3605 as new-default and escalated it to their own captain, because tests/fm-watch-triage.test.sh specifies current behavior (the throttle "must not suppress the new wait's first inspection merely because its timestamp is still young", asserting one wake) and 3605 rewrites that test to assert zero; the underlying concern, citing VISION.md, is that a captain-held: wait IS a pending decision, and a genuinely new declaration would stay silent until PAUSE_RESURFACE_SECS.
Keying the scope on declaration content addresses that objection: a genuinely new declaration changes the key and still surfaces immediately, while a no-change write does not change the key and does not wake.
This change fixes the cause rather than the symptom and is proposed as a replacement for #3605.

What Changed

  • bin/fm-watch.sh: stale_wait_declaration now builds the declared-wait throttle scope from the latest status event itself (verb and reason, as last_status_line reads it) instead of the status file signature (size, identity, mtime). handle_paused_stale, busy_turn_bound_check and surface_nonterminal_stale all use it. A status write that leaves a paused: or captain-held: wait unchanged, such as a repeated identical declaration or continuation prose with no status verb, no longer resets the PAUSE_RESURFACE_SECS cadence or wakes the supervisor early. A new or changed declaration changes the key, so its first inspection still surfaces right away.
  • The recheck age no longer starts from the status file's mtime at every read. The new declared_wait_age records when each declaration was first seen in .paused-since-<key> and keeps that time while the declaration stays the same, so a write that doesn't change the wait doesn't restart the age.
    Age anchor choice: the time the declaration was first seen, taken from the status file mtime at that sighting, lowered only if the file later shows an older mtime. Reason: the age gates the first re-surface and is what the recheck reports, so it must measure the wait, not the latest write; a declaration first seen after later no-change writes anchors on the newest write, which can only delay its first re-surface, never advance it. The record is removed with the rest of the pause state (clear_pause_state in fm-watch.sh, clear_pause_tracking in fm-supervise-daemon.sh). docs/architecture.md now describes the declaration-keyed throttle and age.
  • tests/fm-watch-triage.test.sh: new tests for both paused: and captain-held:. Two writes that keep the wait cause no wake before the cadence. After the cadence, exactly one recheck reports the wait's own age. A changed reason surfaces immediately. The same wait declared word for word after the worker has left it (a working: line in between, or a captain-held [key=X] re-held after a resolve) also surfaces on first inspection on the live-agent path; those tests passed on the head before they were added, so the review fix round added tests only and no code change.

Relation to #3605

This fixes the cause behind #3605 instead of treating the symptom. #3605 moves the age gate so a young replacement wait gets absorbed, which would keep a genuinely new captain-held: decision silent until PAUSE_RESURFACE_SECS. That is the maintainer's objection. Here, a new declaration changes the throttle key and still surfaces on first inspection. The existing assertion that a replacement wait's first inspection is not suppressed keeps passing unchanged. Only writes that leave the wait unchanged are absorbed. I propose this PR as a replacement for #3605.

Scope of the fix

  • Closed: early stale: / re-surface wakes caused by a status write that leaves a declared paused: or captain-held: wait unchanged, and the matching age reset. The measured cases ("paused 91s", "paused 153s", and the re-asked confirmation 86s after it was written) were re-written paused: declaration lines, not note: lines.
  • Remains open: a note: line written under a declared wait still produces a recheck, because the watcher reads the newest status event as ending the wait (the latest-event reader from fix(bin): read the latest status event so buried declarations and open decisions aren't lost #3753). That class is not changed here and is tracked separately. The signal: wake for the status write itself also remains.
  • Not claimed: stale wakes that arrive as "possible wedge, escalation 1" while a run step is live. They go through a different path and were not measured. This change does not promise that declared waits stay fully silent.

Risk Assessment

✅ Low: The change swaps the declared-wait throttle key and the age anchor from the status-file signature to the latest declaration text, and every clearing path still holds: the live path writes .paused-, so the loop-top reconciliation clears the throttle on a non-wait event; in away mode, the daemon's reconcile_pause_tracking clears .stale- on the status signal; and clear_pause_state and the daemon both drop the new .paused-since record. The fix round added tests only, which honestly exercise the identical re-declaration sequence.

Testing

I ran the new and neighbouring declared-wait tests from tests/fm-watch-triage.test.sh on HEAD (all pass). I ran the new tests against the base commit's watcher, where the no-change-write test fails with the reported immediate "paused 0s" wake. I also drove the real fm-watch.sh step by step for both paused: and captain-held: on HEAD and on base, and saved the wake-queue transcripts to the evidence directory. On HEAD, no-change writes stay silent until the interval runs out, the re-check age follows the declaration, and a changed declaration surfaces at once. I did not exercise the note: line case or the wedge-escalation path, as recorded decisions and the intent exclude them. There is no UI, so there are no screenshots. Overall result: go.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
paused: wait - repeating the identical line and appending prose don't wake the supervisor before the interval runs out ✅ pass live transcript-head.txt [paused] step 2: watcher stayed in its poll loop for 4 cycles, 0 stale wakes; transcript-base.txt shows an immediate 'paused 1s' wake for the same writes
captain-held: wait - repeating the identical line and appending prose don't wake the supervisor before the interval runs out ✅ pass live transcript-head.txt [captain-held] step 2: 0 stale wakes; base wakes with 'captain-held 0s'
No-change writes don't reset the age: the re-check after the interval reports the declaration's age, not the time since the write ✅ pass live transcript-head.txt step 3: 'paused 525s' and 'captain-held 525s' (declared 500s earlier); test_absorbed_wait_cadence_survives_a_status_write_that_keeps_the_wait asserts >= 500
A changed reason (a genuinely new declaration) wakes the supervisor on first inspection, for both paused: and captain-held: ✅ pass live transcript-head.txt step 4: exactly 1 wake at 0s/1s for each verb
Adversarial: an identical wait declared again after a working: line (paused) or a resolved line (captain-held) is not throttled by the old marker ✅ pass live test_live_identical_wait_declared_again_after_leaving_it_surfaces runs real fm-watch.sh subprocesses and passes on HEAD: exactly 1 wake
Existing declared-wait behaviour still holds: replacement waits, re-held captain calls, pane churn under the throttle, away-mode (AFK) handoff ✅ pass live targeted runner: the 5 neighbouring tests report ok on HEAD
Evidence: Driver script for the real fm-watch.sh declared-wait sequence

Source: Driver script for the real fm-watch.sh declared-wait sequence

#!/usr/bin/env bash
# Drives the real bin/fm-watch.sh (fake tmux pane, isolated FM_STATE_OVERRIDE) through:
# declared wait -> first stale wake -> ack -> no-change status write -> stale poll
# -> cadence elapsed -> changed declaration. Prints the wake queue after each step.
# Usage: drive-declared-wait-cadence.sh <checkout-root-with-tests>
R=$1
src=$(mktemp); awk 'NR<5240' "$R/tests/fm-watch-triage.test.sh" | sed "s#\$(dirname \"\${BASH_SOURCE\[0\]}\")#$R/tests#" > "$src"
. "$src"
q() { awk -F '\t' '$3=="stale"{print "    WAKE: "$5}' "$state/.wake-queue" 2>/dev/null; n=$(awk -F '\t' '$3=="stale"{n++}END{print n+0}' "$state/.wake-queue" 2>/dev/null); echo "    stale wakes queued (unacked): ${n:-0}"; }
for spec in 'paused|paused: waiting on validation run one|paused: waiting on validation run two' \
            'captain-held|captain-held [key=route]: awaiting the routing call|captain-held [key=route]: awaiting the release call'; do
  label=${spec%%|*}; spec=${spec#*|}; initial=${spec%%|*}; changed=${spec#*|}
  dir=$(make_case "drive-$label"); state="$dir/state"; fakebin="$dir/fakebin"; out="$dir/watch.out"; cap="$dir/pane.txt"; statusf="$state/held.status"; window=test:fm-held
  printf 'window=%s\nkind=ship\nharness=grok\nbackend=tmux\n' "$window" > "$state/held.meta"
  printf '%s\n' "$initial" > "$statusf"; set_mtime "$(( $(date +%s) - 500 ))" "$statusf"
  printf '%s' "$(seen_sig "$statusf")" > "$state/.seen-held_status"
  key=$(printf '%s' "$window" | tr ':/.' '___')
  printf 'idle after agent exit\n' > "$cap"; printf '%s' "$(hash_text 'idle after agent exit')" > "$state/.hash-$key"; printf '1\n' > "$state/.count-$key"
  echo "=== [$label] step 1: wait declared 500s ago -> first stale inspection (expect 1 wake)"
  absorbed_wait_round "$state" "$fakebin" "$out" "$cap" "$window" exit && echo "    watcher exited (surfaced)" || echo "    watcher did NOT surface"
  q; ack_stopped_cycle "$state" >/dev/null && echo "    (acked)"
  for write in "$initial" 'continuation of the same wait, nothing changed'; do
    echo "=== [$label] step 2: status write that keeps the wait: '$write' (expect 0 wakes before cadence)"
    printf '%s\n' "$write" >> "$statusf"; printf '%s' "$(seen_sig "$statusf")" > "$state/.seen-held_status"
    printf 'idle after write: %s\n' "$write" > "$cap"
    absorbed_wait_round "$state" "$fakebin" "$out" "$cap" "$window" absorb && echo "    watcher stayed in its poll loop (absorbed) for 4 cycles" || echo "    watcher EXITED/surfaced"
    q; ack_stopped_cycle "$state" >/dev/null 2>&1 && echo "    (acked a wake)"
  done
  echo "=== [$label] step 3: cadence elapsed (expect 1 recheck, age >= 500s, anchored on declaration)"
  set_mtime "$(( $(date +%s) - 2000 ))" "$state/.paused-resurfaced-$key"; printf 'idle once cadence elapsed\n' > "$cap"
  absorbed_wait_round "$state" "$fakebin" "$out" "$cap" "$window" exit && echo "    watcher exited (surfaced)" || echo "    watcher did NOT surface"
  q; ack_stopped_cycle "$state" >/dev/null 2>&1
  echo "=== [$label] step 4: changed declaration '$changed' (expect immediate wake)"
  printf '%s\n' "$changed" >> "$statusf"; printf '%s' "$(seen_sig "$statusf")" > "$state/.seen-held_status"; printf 'idle after changed\n' > "$cap"
  absorbed_wait_round "$state" "$fakebin" "$out" "$cap" "$window" exit && echo "    watcher exited (surfaced)" || echo "    watcher did NOT surface"
  q
done
Evidence: HEAD transcript: no-change writes absorbed, re-check age anchored on the declaration, changed declaration wakes at once

Source: HEAD transcript: no-change writes absorbed, re-check age anchored on the declaration, changed declaration wakes at once

# HEAD d119a41e
=== [paused] step 1: wait declared 500s ago -> first stale inspection (expect 1 wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (paused 501s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
    stale wakes queued (unacked): 1
    (acked)
=== [paused] step 2: status write that keeps the wait: 'paused: waiting on validation run one' (expect 0 wakes before cadence)
    watcher stayed in its poll loop (absorbed) for 4 cycles
    stale wakes queued (unacked): 0
    (acked a wake)
=== [paused] step 2: status write that keeps the wait: 'continuation of the same wait, nothing changed' (expect 0 wakes before cadence)
    watcher stayed in its poll loop (absorbed) for 4 cycles
    stale wakes queued (unacked): 0
    (acked a wake)
=== [paused] step 3: cadence elapsed (expect 1 recheck, age >= 500s, anchored on declaration)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (paused 525s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
    stale wakes queued (unacked): 1
=== [paused] step 4: changed declaration 'paused: waiting on validation run two' (expect immediate wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (paused 0s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
    stale wakes queued (unacked): 1
=== [captain-held] step 1: wait declared 500s ago -> first stale inspection (expect 1 wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (captain-held 501s, awaiting the captain - verified hold transfer, rechecked on a long cadence not a wedge; answer the held decision or release the hold)
    stale wakes queued (unacked): 1
    (acked)
=== [captain-held] step 2: status write that keeps the wait: 'captain-held [key=route]: awaiting the routing call' (expect 0 wakes before cadence)
    watcher stayed in its poll loop (absorbed) for 4 cycles
    stale wakes queued (unacked): 0
    (acked a wake)
=== [captain-held] step 2: status write that keeps the wait: 'continuation of the same wait, nothing changed' (expect 0 wakes before cadence)
    watcher stayed in its poll loop (absorbed) for 4 cycles
    stale wakes queued (unacked): 0
    (acked a wake)
=== [captain-held] step 3: cadence elapsed (expect 1 recheck, age >= 500s, anchored on declaration)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (captain-held 525s, awaiting the captain - verified hold transfer, rechecked on a long cadence not a wedge; answer the held decision or release the hold)
    stale wakes queued (unacked): 1
=== [captain-held] step 4: changed declaration 'captain-held [key=route]: awaiting the release call' (expect immediate wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (captain-held 1s, awaiting the captain - verified hold transfer, rechecked on a long cadence not a wedge; answer the held decision or release the hold)
    stale wakes queued (unacked): 1
Evidence: Base transcript: each no-change write wakes immediately with 'paused 1s' / 'captain-held 0s' (reproduces the defect)

Source: Base transcript: each no-change write wakes immediately with 'paused 1s' / 'captain-held 0s' (reproduces the defect)

# BASE 3eb5b633 (bin/ from base, test helpers from HEAD)
=== [paused] step 1: wait declared 500s ago -> first stale inspection (expect 1 wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (paused 500s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
    stale wakes queued (unacked): 1
    (acked)
=== [paused] step 2: status write that keeps the wait: 'paused: waiting on validation run one' (expect 0 wakes before cadence)
    watcher EXITED/surfaced
    WAKE: stale: test:fm-held (paused 1s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
    stale wakes queued (unacked): 1
    (acked a wake)
=== [paused] step 2: status write that keeps the wait: 'continuation of the same wait, nothing changed' (expect 0 wakes before cadence)
    watcher EXITED/surfaced
    WAKE: stale: test:fm-held (paused 1s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
    stale wakes queued (unacked): 1
    (acked a wake)
=== [paused] step 3: cadence elapsed (expect 1 recheck, age >= 500s, anchored on declaration)
    watcher did NOT surface
    stale wakes queued (unacked): 0
=== [paused] step 4: changed declaration 'paused: waiting on validation run two' (expect immediate wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (paused 0s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
    stale wakes queued (unacked): 1
=== [captain-held] step 1: wait declared 500s ago -> first stale inspection (expect 1 wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (captain-held 501s, awaiting the captain - verified hold transfer, rechecked on a long cadence not a wedge; answer the held decision or release the hold)
    stale wakes queued (unacked): 1
    (acked)
=== [captain-held] step 2: status write that keeps the wait: 'captain-held [key=route]: awaiting the routing call' (expect 0 wakes before cadence)
    watcher EXITED/surfaced
    WAKE: stale: test:fm-held (captain-held 0s, awaiting the captain - verified hold transfer, rechecked on a long cadence not a wedge; answer the held decision or release the hold)
    stale wakes queued (unacked): 1
    (acked a wake)
=== [captain-held] step 2: status write that keeps the wait: 'continuation of the same wait, nothing changed' (expect 0 wakes before cadence)
    watcher EXITED/surfaced
    WAKE: stale: test:fm-held (captain-held 1s, awaiting the captain - verified hold transfer, rechecked on a long cadence not a wedge; answer the held decision or release the hold)
    stale wakes queued (unacked): 1
    (acked a wake)
=== [captain-held] step 3: cadence elapsed (expect 1 recheck, age >= 500s, anchored on declaration)
    watcher did NOT surface
    stale wakes queued (unacked): 0
=== [captain-held] step 4: changed declaration 'captain-held [key=route]: awaiting the release call' (expect immediate wake)
    watcher exited (surfaced)
    WAKE: stale: test:fm-held (captain-held 0s, awaiting the captain - verified hold transfer, rechecked on a long cadence not a wedge; answer the held decision or release the hold)
    stale wakes queued (unacked): 1

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • ⚠️ bin/fm-watch.sh:1369 - If a worker declares the same wait again word for word after going back to work, the stale path now stays silent until the cadence runs out. The throttle scope is now just declared:&lt;latest event text&gt;. The .paused-resurfaced-&lt;key&gt; marker is only cleared by the per-poll check at fm-watch.sh:2440, and that check requires .paused-$key to exist. Only handle_paused_stale writes that file. The live-agent path (surface_nonterminal_stale) and the away-mode busy handoff (busy_turn_bound_check, which writes .stale-$key) never create it.

Concrete sequence with a live agent: (1) paused: waiting on CI goes stale. The first sight wakes and the marker becomes declared:paused: waiting on CI. (2) The worker writes working: fixing the CI failure. Nothing clears the marker, because .paused-$key is absent. (3) Within PAUSE_RESURFACE_SECS the worker writes paused: waiting on CI again for a new run. The pane goes stale, stale_wait_throttled matches the old marker, and the new wait's first stale inspection is absorbed. Before this change the file signature differed, so it surfaced. The same happens for a captain-held [key=X]: &lt;same reason&gt; held again after a resolve, and in away mode the daemon's one-shot .stale-$key is not re-fired either. The status write still produces its own signal: wake, so the event isn't completely invisible. But the intent requires that "a genuinely new declaration still surfaces immediately", and this is the maintainer's concern on #3605: a captain-held wait is a pending decision.

Possible fixes: when the latest event is not a declared wait, clear a declared:-prefixed .paused-resurfaced-&lt;key&gt; (and a declared: .stale-&lt;key&gt;) whatever .paused-$key says. Or accept identical re-declaration as "the same wait". This is a product choice about what counts as a new declaration, so the author should decide.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
paused: wait - repeating the identical line and appending prose don't wake the supervisor before the interval runs out ✅ pass live transcript-head.txt [paused] step 2: watcher stayed in its poll loop for 4 cycles, 0 stale wakes; transcript-base.txt shows an immediate 'paused 1s' wake for the same writes
captain-held: wait - repeating the identical line and appending prose don't wake the supervisor before the interval runs out ✅ pass live transcript-head.txt [captain-held] step 2: 0 stale wakes; base wakes with 'captain-held 0s'
No-change writes don't reset the age: the re-check after the interval reports the declaration's age, not the time since the write ✅ pass live transcript-head.txt step 3: 'paused 525s' and 'captain-held 525s' (declared 500s earlier); test_absorbed_wait_cadence_survives_a_status_write_that_keeps_the_wait asserts >= 500
A changed reason (a genuinely new declaration) wakes the supervisor on first inspection, for both paused: and captain-held: ✅ pass live transcript-head.txt step 4: exactly 1 wake at 0s/1s for each verb
Adversarial: an identical wait declared again after a working: line (paused) or a resolved line (captain-held) is not throttled by the old marker ✅ pass live test_live_identical_wait_declared_again_after_leaving_it_surfaces runs real fm-watch.sh subprocesses and passes on HEAD: exactly 1 wake
Existing declared-wait behaviour still holds: replacement waits, re-held captain calls, pane churn under the throttle, away-mode (AFK) handoff ✅ pass live targeted runner: the 5 neighbouring tests report ok on HEAD
  • bash tests/.nm-targeted.test.sh (temporary runner, since removed) on HEAD: test_absorbed_replacement_wait_does_not_inherit_the_old_throttle, test_absorbed_wait_cadence_survives_a_status_write_that_keeps_the_wait, test_live_declared_wait_churn_honors_the_resurface_throttle, test_live_identical_wait_declared_again_after_leaving_it_surfaces, test_reheld_captain_call_starts_its_own_resurface_window, test_nonterminal_stale_paused_absorbed_then_resurfaced, test_afk_paused_changed_pane_hands_off_plain_stale: all ok
  • Same new tests against base 3eb5b633's bin/ via git archive: test_absorbed_wait_cadence_survives_a_status_write_that_keeps_the_wait fails with a 'paused 0s' stale wake (reproduces the defect); the re-declaration test passes on base too, so it guards against a regression
  • drive-declared-wait-cadence.sh &lt;root&gt; runs real fm-watch.sh rounds (declare, ack, repeated identical line, continuation prose, interval elapsed, changed reason) for paused: and captain-held:, on HEAD and on base, printing the wake queue after each step
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…e status file

A status write that left a paused: or captain-held: wait as declared (a
repeated identical line, continuation prose) changed the status file
signature the re-surface throttle was scoped to, so the watcher re-surfaced
the wait at once and reported its age from the write instead of the
declaration.

Scope the throttle to the latest status event as last_status_line reads it
(verb and reason), and anchor the wait's age on when that declaration was
first seen, recorded beside it in .paused-since-<key>. A changed verb or
reason is a new declaration and still surfaces on first inspection.
@mremond

mremond commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

All checks are green at the current head and this has not been triaged yet. Could it get a look? It replaces #3605, and #4818 builds on it. Happy to resolve the conflict with main if you would like.

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