Skip to content

fix(gateway): close the F2 self-completing-restart-loop gap (A' record-mark-at-gate + C1 skill detection) - #72

Merged
Kyzcreig merged 3 commits into
mainfrom
fix/f2-self-restart-loop
Jun 22, 2026
Merged

Kyzcreig merged 3 commits into
mainfrom
fix/f2-self-restart-loop

Conversation

@Kyzcreig

Copy link
Copy Markdown
Collaborator

What & why

Closes the one residual the cascade fix (#70) left behind — and which was originally WONTFIX'd on a false premise. F2 (the restart-loop circuit-breaker) defined "forward progress" as "a clean turn completed," and cleared the breaker on any clean turn. So a session that completes a clean turn AND re-fires a restart every cycle (an autonomous self-restart / cron-resume shape) reset the breaker each cycle and looped forever — undetected by:

  • F1 — only tags request_restart//restart, not the dominant safe-restart skill path.
  • F2 — reset every cycle by the clean completion.
  • F3 — single-flight only catches concurrent watchers; this loop is sequential.

Grounding the "F1+F3 catch it" claim against the code disproved it (F1's _session_initiated_restart is set only by request_restart, not the skill's Popen of the watcher), so this ships a real fix rather than a documented limitation.

The fix (A′ + C1)

A′ — record-mark-at-gate. Extracted the post-clean-turn logic into _apply_post_turn_resume_gate(). If the turn itself initiated a restart (_session_initiated_restart, popped one-shot), record a replay-mark — the same _record_restart_replay_mark call / threshold / suspend+alert the drain path uses — instead of clearing the breaker. A restart-only "clean" turn is loop progress, not work progress, so it accrues and F2 suspends at threshold. Real-work turns still clear (the parent anti-false-trip behavior is byte-preserved in the else branch).

C1 — skill-path observability. The safe-restart skill runs as a terminal tool call in-process; progress_callback now sets _session_initiated_restart when it observes a terminal command that invokes safe-restart.py — via a new _command_invokes_safe_restart matcher that excludes inspection verbs (cat/grep/vim/echo/…) so merely reading the script can't trip it. Both restart paths now converge on the same in-process flag. No sentinel file, no new model tool (purely internal inspection of a tool event the gateway already receives).

Threshold/window reuse the parent fix's restart_loop_threshold (3) / restart_loop_window_secs (300) — same failure class.

Tests — all RED-proven (tests/gateway/test_restart_cascade.py, +7 → 20 total)

  • test_self_completing_restart_loop_is_suspended — the loop is suspended at threshold (RED: revert A′ → loops forever).
  • test_single_self_restart_then_real_work_not_suspended / test_real_work_turns_never_accrue_marks — feature + parent anti-false-trip preserved (INV-2/INV-3).
  • test_initiated_flag_is_one_shot_per_turn — flag popped per turn, no bleed.
  • test_skill_path_progress_callback_sets_initiated_flag — C1 trips on invoke, not on inspect.
  • test_command_invokes_safe_restart_matcher — direct matcher unit coverage (exec vs inspect vs pipeline), drives the real function.
  • test_c1_detection_present_in_real_progress_callback — source-guard that the live C1 branch exists (the behavioral C1 test uses a mirror because the real callback is a closure; this ties the mirror to reality, RED: delete the branch → fails).

Full blast radius green: test_restart_cascade (20) + test_restart_resume_pending/test_restart_drain/test_resume_command/test_clean_shutdown_marker/test_restart_notification (163 total).

Review

Spec reviewed (APPROVE-WITH-CHANGES → R1 "record, don't skip-clear" + R2 "C1 over C2 sentinel" folded into v2). Diff reviewed by two independent passes (APPROVE + APPROVE-WITH-CHANGES, no BLOCK); the one substantive finding — the cat safe-restart.py false-positive — is fixed by the inspection-verb exclusion + tested. Cache/alternation/single-writer invariants untouched; no new core tool, no new env-for-config.

Spec: ~/.hermes/plans/2026-06-21_f2-self-completing-restart-loop-fix.md. Builds on #70.

The parent cascade fix (#70) left one residual that was WONTFIX'd on a FALSE
premise: F2's forward-progress gate cleared the breaker on ANY clean turn, so an
agent that completes a clean turn AND re-fires a restart every cycle (autonomous
self-restart / cron-resume shape) looped forever — undetected by F1 (only tags
request_restart/\/restart, NOT the safe-restart skill), F2 (reset every cycle),
or F3 (only concurrent watchers; this loop is sequential).

A' (record-mark-at-gate): extract the post-clean-turn logic into
_apply_post_turn_resume_gate(). If the turn itself initiated a restart
(_session_initiated_restart, popped one-shot), RECORD a replay-mark — same call /
threshold / suspend+alert the drain path uses — instead of clearing the breaker.
A restart-only 'clean' turn is loop progress, not work progress, so it accrues
and F2 suspends it at threshold. Real-work turns still clear (parent
anti-false-trip behavior byte-preserved in the else branch).

C1 (skill-path observability): the safe-restart SKILL runs as a  tool
call in-process; progress_callback now sets _session_initiated_restart when it
observes a terminal command that INVOKES safe-restart.py (via the new
_command_invokes_safe_restart matcher, which excludes inspection verbs like
cat/grep/vim so merely reading the script can't trip it). Both restart paths now
converge on the same in-process flag. No sentinel file, no new model tool.

Tests (tests/gateway/test_restart_cascade.py, +7; all RED-proven): self-loop
suspended; single-self-restart-then-work not suspended; real-work never marks;
one-shot flag; C1 sets-flag-on-invoke / not-on-inspect; matcher unit coverage
(exec vs inspect vs pipeline); source-guard that the live C1 branch exists.
Full blast radius green (163 incl. resume/drain/notification). Reviewed: spec
APPROVE-WITH-CHANGES (R1 record-not-skip, R2 C1-over-C2 folded), diff APPROVE +
APPROVE-WITH-CHANGES (false-positive tightening applied).
@Kyzcreig
Kyzcreig enabled auto-merge (squash) June 22, 2026 00:52
@github-actions

github-actions Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

🔎 Lint report: fix/f2-self-restart-loop vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11252 on HEAD, 11252 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5877 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@greptile-apps

greptile-apps Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR closes the one residual loop-detection gap left by the cascade fix (#70): a session that completes a clean turn and immediately re-fires a restart every cycle was invisible to F1/F2/F3 because F2 cleared its breaker on any clean turn. Two additions fix this: A′ extracts post-turn logic into _apply_post_turn_resume_gate, which records a replay-mark instead of clearing the breaker when the turn itself initiated a restart, letting marks accumulate until suspension; C1 sets _session_initiated_restart from the progress_callback when it observes a terminal tool start invoking safe-restart.py, so the dominant skill path converges on the same in-process flag as request_restart().

  • A′ gate (_apply_post_turn_resume_gate): restart-only turns accrue marks, real-work turns clear them; the parent anti-false-trip invariant is byte-preserved in the else branch.
  • C1 skill detection (_command_invokes_safe_restart): tokenises terminal commands per segment, skips a curated list of inspection verbs so cat safe-restart.py is excluded, and the real function is exercised by the test suite—not a mirror.
  • Tests (+7 → 20 total): each new test is described as RED-proven against the relevant revert, including a source-guard that ties CI to the live branch's existence.

Confidence Score: 5/5

Safe to merge; the gate and C1 detection logic is correct, the seven new tests are tied directly to the real implementation functions, and the anti-false-trip invariants from the parent fix are preserved.

The A′ gate correctly routes restart-initiated turns to mark accumulation and real-work turns to mark clearing, with the one-shot pop preventing bleed-through. C1 skill detection is accurately scoped to tool.started events for terminal commands that name the script without being inspection verbs. All new tests exercise the real functions, and the source-guard test ties CI to the live branch. The only new finding is a gap in the inspection-verb list that could produce spurious marks in unusual agent turns but does not affect the nominal restart-loop or legitimate-deploy paths.

gateway/run.py — specifically the _SAFE_RESTART_INSPECTION_VERBS tuple and the _apply_post_turn_resume_gate logic; the concerns raised in earlier review rounds about the missing _resumed_this_boot guard and tool.started timing are worth re-evaluating before the next change to this area.

Important Files Changed

Filename Overview
gateway/run.py Two additions: _command_invokes_safe_restart module-level matcher (with inspection-verb exclusion list) and _apply_post_turn_resume_gate instance method that records marks instead of clearing them when a turn-initiated restart is detected; C1 branch wired into the existing progress_callback closure. Logic is correct; previous-review concerns about the missing _resumed_this_boot guard and tool.started-before-completion timing remain open.
tests/gateway/test_restart_cascade.py Seven new tests cover the core loop suspension, one-shot flag invariant, anti-false-trip preservation, C1 callback detection (invoke vs. inspect), direct matcher coverage, and a source-guard asserting the live branch exists. All clearly structured and tied to the real implementation functions.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Boot as Gateway Boot
    participant Runner as GatewayRunner
    participant CB as progress_callback (C1)
    participant Gate as _apply_post_turn_resume_gate (A')
    participant Marks as replay_marks store
    participant Drain as _mark_resume_pending_for_shutdown

    Boot->>Runner: "auto-resume session (resume_pending=True)"
    Runner->>Runner: _resumed_this_boot.add(session_key)
    Note over Runner: agent turn begins

    Runner->>CB: "event=tool.started, tool=terminal"
    CB->>Runner: "_session_initiated_restart[sk] = True"

    Note over Runner: agent turn completes cleanly

    Runner->>Gate: _apply_post_turn_resume_gate(sk)
    Gate->>Gate: "pop _session_initiated_restart[sk] -> True"
    Gate->>Marks: _record_restart_replay_mark(sk)
    Marks-->>Gate: "marks=[t1], count=1 (below threshold)"
    Gate->>Runner: session_store.clear_resume_pending(sk)

    Runner->>Drain: "stop() -> _mark_resume_pending_for_shutdown(sk)"
    Drain->>Runner: session_store.mark_resume_pending(sk)
    Note over Boot: gateway restarts, session re-resumed

    loop Each restart-only cycle
        Runner->>Gate: _apply_post_turn_resume_gate(sk)
        Gate->>Marks: _record_restart_replay_mark(sk)
        Marks-->>Gate: marks grow
    end

    Marks-->>Runner: "marks >= restart_loop_threshold(3)"
    Runner->>Runner: "session_entry.suspended = True"
    Note over Runner: F2 circuit-breaker trips - loop halted
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Boot as Gateway Boot
    participant Runner as GatewayRunner
    participant CB as progress_callback (C1)
    participant Gate as _apply_post_turn_resume_gate (A')
    participant Marks as replay_marks store
    participant Drain as _mark_resume_pending_for_shutdown

    Boot->>Runner: "auto-resume session (resume_pending=True)"
    Runner->>Runner: _resumed_this_boot.add(session_key)
    Note over Runner: agent turn begins

    Runner->>CB: "event=tool.started, tool=terminal"
    CB->>Runner: "_session_initiated_restart[sk] = True"

    Note over Runner: agent turn completes cleanly

    Runner->>Gate: _apply_post_turn_resume_gate(sk)
    Gate->>Gate: "pop _session_initiated_restart[sk] -> True"
    Gate->>Marks: _record_restart_replay_mark(sk)
    Marks-->>Gate: "marks=[t1], count=1 (below threshold)"
    Gate->>Runner: session_store.clear_resume_pending(sk)

    Runner->>Drain: "stop() -> _mark_resume_pending_for_shutdown(sk)"
    Drain->>Runner: session_store.mark_resume_pending(sk)
    Note over Boot: gateway restarts, session re-resumed

    loop Each restart-only cycle
        Runner->>Gate: _apply_post_turn_resume_gate(sk)
        Gate->>Marks: _record_restart_replay_mark(sk)
        Marks-->>Gate: marks grow
    end

    Marks-->>Runner: "marks >= restart_loop_threshold(3)"
    Runner->>Runner: "session_entry.suspended = True"
    Note over Runner: F2 circuit-breaker trips - loop halted
Loading

Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/f2-self-res..." | Re-trigger Greptile

Comment thread gateway/run.py
Comment on lines +557 to +562
if not cmd or "safe-restart.py" not in cmd:
return False
# Tokenize loosely; the first meaningful token decides intent. A pipeline or
# &&-chain that contains an execution elsewhere still counts (we scan segments).
import shlex
for segment in cmd.replace("&&", "\n").replace("|", "\n").replace(";", "\n").splitlines():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The import shlex statement is placed inside the function body rather than at the module level. Python caches imports in sys.modules so there is no runtime penalty, but the unconventional placement can confuse readers who expect all stdlib imports at the top of the file. The rest of the module follows the top-of-file import convention.

Suggested change
if not cmd or "safe-restart.py" not in cmd:
return False
# Tokenize loosely; the first meaningful token decides intent. A pipeline or
# &&-chain that contains an execution elsewhere still counts (we scan segments).
import shlex
for segment in cmd.replace("&&", "\n").replace("|", "\n").replace(";", "\n").splitlines():
if not cmd or "safe-restart.py" not in cmd:
return False
# Tokenize loosely; the first meaningful token decides intent. A pipeline or
# &&-chain that contains an execution elsewhere still counts (we scan segments).
for segment in cmd.replace("&&", "\n").replace("|", "\n").replace(";", "\n").splitlines():

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread gateway/run.py
Comment on lines +4773 to +4787
if initiated_restart:
try:
self._record_restart_replay_mark(session_key)
except Exception as exc:
logger.debug(
"record_restart_replay_mark failed for %s: %s", session_key, exc
)
# Do NOT clear replay_marks / _resumed_this_boot — this was loop
# progress, not work progress.
else:
self._clear_restart_replay_marks(session_key)
try:
self._resumed_this_boot.discard(session_key)
except Exception:
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Gate records marks for non-resumed sessions

_apply_post_turn_resume_gate records a replay-mark whenever _session_initiated_restart is set, regardless of whether the session is in _resumed_this_boot. The drain path has an explicit session_key in _resumed_this_boot guard before calling _record_restart_replay_mark. Without that guard here, a session on its very first run (never auto-resumed) can start accruing marks. In practice this means a session that genuinely uses the safe-restart skill three times within the window (e.g., three rolling deploys in under five minutes) would be suspended before completing a full resume cycle, even though no loop has occurred. The test test_skill_path_progress_callback_sets_initiated_flag explicitly exercises this path without adding the session to _resumed_this_boot, confirming the broader scope is intentional — but the PR description frames the fix narrowly as a resume-loop guard, so the widened scope is worth a conscious decision rather than an implicit consequence.

Comment thread gateway/run.py
Comment on lines +14360 to +14372
if session_key and tool_name == "terminal":
_cmd = ""
if isinstance(args, dict):
_cmd = str(
args.get("command")
or args.get("cmd")
or args.get("script")
or ""
)
if not _cmd:
_cmd = str(args or "")
if _command_invokes_safe_restart(_cmd):
self._session_initiated_restart[session_key] = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Marks accrue on tool.started, before execution outcome is known

C1 sets _session_initiated_restart on the tool.started event, before the terminal tool returns a result. If safe-restart.py exits non-zero (e.g., the handoff target isn't ready), no gateway restart occurs, but the flag is already set. When _apply_post_turn_resume_gate runs at turn end, it pops the flag as True and records a mark even though no loop iteration happened. Three such failed invocations within the window would suspend the session. For the nominal success path this is a non-issue (the old gateway shuts down before another turn runs), but a session repeatedly attempting a failed restart could be suspended via the breaker rather than the failure-count path. Whether treating failed invocations as "loop progress" is acceptable is a product call; the current behaviour is at least deterministic and auditable via logger.debug.

Kyzcreig and others added 2 commits June 21, 2026 18:03
…_restart

CI caught it: test_session_hygiene.py builds GatewayRunner via object.__new__
(bypassing __init__), so _session_initiated_restart is unset there. Use the same
getattr(self, '_session_initiated_restart', {}) self-heal the parent fix uses at
_resume_reason_for_shutdown_mark (run.py:4241). 8 hygiene tests RED on clean
reproduce → green after; 189 blast-radius green.
@Kyzcreig
Kyzcreig merged commit a2459a6 into main Jun 22, 2026
37 checks passed
Kyzcreig added a commit that referenced this pull request Jun 22, 2026
…er (#80)

* fix(gateway): authoritative restart-initiator breadcrumb for F2 breaker

The F2 self-completing-restart-loop breaker (#70/#72) detects which session
initiated a restart via C1, a command-string heuristic that matches the literal
`safe-restart.py` in a terminal tool call. That silently false-NEGATIVES if the
skill is invoked via an alias/wrapper/rename — the breaker quietly stops arming
for that path, discovered only during the cascade it exists to prevent.

Replace the heuristic's authority with a self-declared breadcrumb: safe-restart.py
(which runs synchronously in the initiating turn and knows its own session key)
drops a per-session, per-boot file in HERMES_HOME; the clean-turn gate consumes it
(read+unlink) independent of how the command was spelled. C1/F1 retained as
defense-in-depth fallback.

- boot_id (pid:psutil.create_time) gates cross-boot crumbs → kills the false-trip
  vector where an interrupted initiator's crumb marks the next real-work turn.
  Gateway is the SOLE boot_id producer (writes it to gateway_state.json); the
  script copies the string verbatim — no parser drift (macOS /proc returns None,
  so the prior start_time field was pid-only; psutil is cross-platform).
- Per-session files → no shared-list RMW race / lost-update.
- Always-consume (no `flag or consume()` short-circuit) so a C1-flagged turn
  can't leak its crumb. Startup sweep + finally-consume bound any leak.
- 16 new tests (RED-proven), incl. a real-subprocess round-trip proving detection
  works when the gateway never sees the command line.

Spec: 2026-06-22_f2-initiator-detection-authoritative-breadcrumb.md (2 Opus passes
+ Phase-0 live probe). Parent: #72.

* fix(gateway): diff-review BLOCK fixes — persist boot_id, degraded-id fail-safe

Final diff review (2 Opus reviewers, both BLOCK) found the feature was inert
after restart #1 and the tests masked it. Fixes:

- BLOCKER-1: write_runtime_status now refreshes boot_id on EVERY write (status.py).
  gateway_state.json survives reboots, so the prior boot's boot_id persisted
  while pid updated → the gate's boot check mismatched every breadcrumb →
  feature silently inert. One-line producer fix restores "single producer writes
  the current boot's id."
- BLOCKER-2: round-trip tests now drive the REAL write_runtime_status producer
  with a stale prior-boot file (stale_first=True), not a hand-seeded state file
  that masked the bug. New test_real_producer_refreshes_boot_id_across_restart
  is RED against the pre-fix producer.
- MAJOR-1: consume side rejects a degraded pid-only boot_id ("1234:") on either
  side — can't prove same-boot, fall back to C1/F1 (pid reuse defense).
- MINOR-1: honest comment on the I-8 hash-back (integrity vs the 0700-dir authZ).
- MINOR-2: --no-spawn no longer plants a breadcrumb unless --write-breadcrumb is
  also passed → a diagnostic run under a live gateway can't inject a mark.

All 42 cascade tests + 135 blast-radius green; BLOCKER-1 + MAJOR-1 RED-proven.

* fix(gateway): diff-review-2 polish — boot_id degraded WARN, sweep symmetry, CI-safe contract

Second diff review (2 Opus reviewers, both APPROVE-WITH-CHANGES; all 5 prior
BLOCK/findings verified CLOSED). Non-blocking pre-merge items addressed:

- _compute_boot_id logs a WARNING when it falls back to pid-only (psutil failure)
  so the otherwise-silent fail-closed-inert state is observable (both reviewers
  flagged HIGH).
- Sweep applies the same degraded-boot_id short-circuit as the consume side
  (symmetry): a degraded current boot reaps all crumbs instead of trusting a
  same-pid match. + test_sweep_degraded_boot_reaps_all (RED-proven).
- CI-safe cross-process contract test (test_cross_process_breadcrumb_contract_ci_safe):
  the real-script round-trip tests pytest.skip off-host, so the anti-masking proof
  could vacuum on CI. This replicates the script's breadcrumb-write contract inline
  (real producer + real gate, no skill-script dependency) so the gate side of the
  cross-process contract runs on every host.
- safe-restart.py: unique tmp suffix (pid+uuid) for the breadcrumb atomic write
  (no same-session concurrent-restart race); dead None branch removed from
  --print-home.

44 cascade + 137 blast-radius + 25 skill green; sweep symmetry RED-proven.

---------

Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
Kyzcreig added a commit that referenced this pull request Jul 11, 2026
…istration (CI harness-constructed runner attr-miss — the #72 lesson class)
Kyzcreig added a commit that referenced this pull request Jul 12, 2026
* fix(gateway): fail open startup restore intake

Start the restore deadline at gate arm, release before replay, and retain queued events under a single retrying replay owner. Preserve same-session FIFO, transfer ownership after live cancellation, and keep permanent lifecycle telemetry.

Verified: scripts/run_tests.sh tests/gateway/test_restart_resume_pending.py tests/gateway/test_restart_cascade.py tests/gateway/test_auto_continue_interrupted_turns.py -q (185 passed); targeted regressions 14/14 RED on fork/main; py_compile; git diff --check; Momus pass 5 APPROVE.

* docs: update startup-restore progress ledger

Record PR #301 and the CI, auto-merge, and Aegis-first deployment sequence.

* fix(gateway): getattr self-heal for _background_tasks at watchdog registration (CI harness-constructed runner attr-miss — the #72 lesson class)

* fix(gateway): Greptile P1 — exception path schedules a bounded successor drain (mirrors cancellation path; cap 3, honest retained-count log)

---------

Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
Kyzcreig added a commit that referenced this pull request Jul 18, 2026
…258)

Re-lands #258 (author: Kyzcreig; branch was 836 commits behind) onto current main with all three LIVE Greptile races fixed: (1) the ack call is wrapped so an unexpected exception can never break the load-bearing queue path; (2) the dedupe key is only burned AFTER the adapter check, so an adapter that registers later can still ack that chat; (3) the fire-and-forget ack re-checks _startup_restore_in_progress at send time, so a stale ack can never land after the gate released and the real response went out. Also hardens _background_tasks with the getattr self-heal pattern (fork #72 CI attr-miss class). 8 tests: the original 5 + one per race, all RED-meaningful.

Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
Kyzcreig added a commit that referenced this pull request Jul 18, 2026
…258) (#396)

* fix(gateway): ack a message queued during startup-restore (re-land of #258)

Re-lands #258 (author: Kyzcreig; branch was 836 commits behind) onto current main with all three LIVE Greptile races fixed: (1) the ack call is wrapped so an unexpected exception can never break the load-bearing queue path; (2) the dedupe key is only burned AFTER the adapter check, so an adapter that registers later can still ack that chat; (3) the fire-and-forget ack re-checks _startup_restore_in_progress at send time, so a stale ack can never land after the gate released and the real response went out. Also hardens _background_tasks with the getattr self-heal pattern (fork #72 CI attr-miss class). 8 tests: the original 5 + one per race, all RED-meaningful.

Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>

* fix(gateway): un-burn ack dedupe key on create_task RuntimeError (Greptile #396)

---------

Co-authored-by: Apollo <apollo@kyzcreig.local>
Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
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