This repository was archived by the owner on Jul 16, 2026. It is now read-only.
Repository navigation
feat(bridges): recover orphan .sending files on startup (Phase 5.11) - #79
Merged
Merged
Conversation
Stando's telegram-bridge and discord-bridge both use atomic-claim-by-
rename for proactive-delivery — `results/proactive-*.txt` is renamed
to `proactive-*.sending` before the actual send to prevent same-tick
double-deliveries between concurrent poll iterations. But if the
bridge process crashes between the rename and the send (SIGKILL,
OOM, unhandled exception in the send path), the `.sending` file is
orphaned in `results/` — no poll iteration looks at `.sending`
suffixes, so the owner's proactive notification is silently dropped
until manual intervention.
This PR ports OSS's startup recovery sweep
(`_recover_orphan_sending_files`) to both bridges. On bridge boot,
scan `results/` for orphan `proactive-*.sending` files and rename
them back to `*.txt` so the next poll iteration picks them up.
Changes
-------
- src/telegram-bridge.py: add `_recover_orphan_sending_files` (37
lines) before `main()`; call it from `main()` after
`_single_instance_acquire`, before the polling loop.
- src/discord-bridge.py: add the same function (47 lines, with the
richer docstring from OSS) before `on_ready`; call it from
`on_ready` after the Phase 5.10 auto-seed block, before the
poll-task creation.
Functions are byte-identical to OSS sutando's versions (modulo the
"Mirrors discord-bridge.py" / "Mirrors telegram-bridge.py" docstring
swap, which points each at the other for cross-bridge consistency).
Pattern matches the OSS comment chain.
Tests (tests/bridge-orphan-sending-recovery.test.py, 9 cases)
-------------------------------------------------------------
1. Behavior parity — both bridges recover 2 orphan files end-to-end.
2. Drift guards — both bridges DEFINE the function AND call it from
their startup paths (regex-checked against `main()` and
`on_ready()`).
3. Selectivity — only `proactive-*.sending` is touched; `task-*.sending`,
`proactive-*.txt.tmp`, and existing `.txt` files pass through.
4. Collision safety — if `<name>.sending` AND `<name>.txt` both exist,
the .sending is left in place (no clobber).
5. Idempotency — second call with no orphans returns 0; missing
RESULTS_DIR also returns 0 (boot before any results materialize).
Minimal-divergence accounting
-----------------------------
- Diff impact on telegram-bridge.py: -0 +56. Pure additive.
Closes a stando→OSS divergence point: OSS had this function,
stando didn't.
- Diff impact on discord-bridge.py: -0 +59. Pure additive.
Closes a stando→OSS divergence point: OSS had this function,
stando didn't.
- Function bodies are byte-identical to OSS upstream. Zero conflict
surface against future OSS pulls.
- Net effect: stando's bridges are now byte-closer to OSS on a
~115-line gap that previously had no equivalent code on the
stando side.
Verification
------------
- python3 -m py_compile on both modified bridges: OK.
- New tests: 9 cases, all green.
- Full Phase 5 regression sweep: 27 Python tests green
(discord-bridge-{allowlist, file-markers, file-send-allowlist,
pending-replies, prose-marker, reply-chunks, state-prefetch,
access-tier, discord-state-detection, welcome-on-first-post,
reply-directive}, dm-result-{send-dm, upload}, discord-config,
send-allowlist, bridge-marker-no-leak, result-markers,
discord-chunker, single-instance, telegram-bridge-{allowlist,
access, tofu, forward-attribution, proactive-owner-resolution},
agent-api-result-archive, health-check-phase-57, plus this PR's
new test).
- `pnpm typecheck` clean.
Phase 5.11 of the OSS->private sync. The other OSS additions to
telegram-bridge.py audited in this phase (the `task_id` parameter
on `send_reply` and the `task_archive.find_task_file` import) were
NOT ported: the first is an additive enhancement that mostly
benefits OSS's outbox_log telemetry rather than stando's
_emit_channel_metric pipeline, and the second is a dependency of
the `claim_task` rename pattern stando does not use. Both can be
revisited if the underlying systems converge later.
Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 5.11 of the OSS→private sync. Pure-additive port of
_recover_orphan_sending_filesfrom OSS sutando to both stando bridges (telegram + discord), closing a bug class that silently dropped proactive notifications when the bridge crashed mid-send.The bug class
Both bridges use atomic-claim-by-rename in proactive delivery:
results/proactive-*.txtis renamed toproactive-*.sendingbefore the send call, preventing same-tick double-deliveries between concurrent poll iterations. But if the bridge crashes BETWEEN the rename and the actual send (SIGKILL, OOM, unhandled exception), the.sendingfile is orphaned forever — no poll iteration looks at.sendingsuffixes.OSS already had a fix in both bridges. Stando inherited the
.sendingclaim pattern but never inherited the recovery. This PR closes that.The fix
src/telegram-bridge.pymain()after_single_instance_acquire, before polling loopsrc/discord-bridge.pyon_ready()after Phase 5.10 auto-seed, beforecreate_task(poll_*)Function bodies are byte-identical to OSS upstream. Pattern: scan
RESULTS_DIRforproactive-*.sendingfiles, rename each back to*.txt, log per-file recovery + total count. Fail-open on per-file errors so a single bad file doesn't block bridge startup.Audit notes — what was NOT ported
send_reply(task_id: str | None = None)parameteroutbox_log.append(...)but doesn't fit stando's_emit_channel_metriccloud pipeline. Stando is architecturally ahead here.from task_archive import find_task_fileclaim_taskrename pattern. Stando doesn't useclaim_task. Inert if ported.These are revisited if the underlying systems converge later.
Minimal-divergence accounting
Test plan
New test
tests/bridge-orphan-sending-recovery.test.py— 9 cases covering both bridges:proactive-*.sendingis touched;task-*.sending, atomic-write temps, and existing.txtfiles pass through.<name>.sendingAND<name>.txtboth exist, the .sending is left in place.RESULTS_DIRalso returns 0.Regression sweep (all green)
pnpm typecheckclean.Manual verification (post-merge)
On the next bridge restart, watch the startup output. If the previous process left orphan
.sendingfiles behind, you'll see lines like:And the proactive notification will deliver on the very next poll iteration instead of being silently dropped.
Made with Cursor