From e32a7f55cbb877b978e7012b300377361a227c21 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 11 Jun 2026 15:01:53 -0500 Subject: fix(triage): correct telegram mark-read verbs and crash guidance The documented mark-read verb telega-chat--mark-read never existed in telega. I replaced it with the verified telega--viewMessages form (plus mentions and reactions), noted that telega-chat-toggle-read toggles and needs an unread guard, and added the delete-join-notice sweep Craig approved (first run deleted 41 chats). The SEGFAULT gotcha now reflects reality: the dockerized server crashes spontaneously (memory corruption, 11 coredumps since 2026-06-09), the verbs were never the trigger, so action batches check the server first and treat a death as retryable. --- .ai/workflows/triage-intake.telegram.org | 55 +++++++++++++++++----- .../.ai/workflows/triage-intake.telegram.org | 55 +++++++++++++++++----- 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/.ai/workflows/triage-intake.telegram.org b/.ai/workflows/triage-intake.telegram.org index d855fcf..a318f12 100644 --- a/.ai/workflows/triage-intake.telegram.org +++ b/.ai/workflows/triage-intake.telegram.org @@ -157,14 +157,24 @@ stays non-nil). =telega-server-kill= is what actually stops the server. Call left in =docker ps=. Skipping this whole branch when =TELEGA_WAS_RUNNING= is t is the point of Step 0: never tear down a session Craig is actively using. -⚠ *SEGFAULT GOTCHA — docker mode is the fix.* The tdlib =telega-server= can exit -abnormally (code 139, SIGSEGV) during or just after the initial sync. Observed -on the 2026-06-09 first run; the cure was *docker mode* — =telega-use-docker= = t -with the =zevlg/telega-server:latest= image (already pulled on Craig's box). -Restarted under docker, the server reached "Ready" in ~2s, synced 73 chats, and -held through a live re-scan with no crash. So: *ensure docker mode is on before -the sweep.* If =telega-use-docker= is nil, set it (=(setq telega-use-docker t)=) -and restart telega. +⚠ *SEGFAULT GOTCHA — crashes are spontaneous; treat server death as routine.* +The dockerized =telega-server= (=zevlg/telega-server:latest=, image built +2026-06-04, tdlib 1.8.64) SIGSEGVs (exit 139) *on its own*, minutes-to-hours +into a session — 11 host coredumps between 2026-06-09 and 2026-06-11, several at +times when no triage verb was running. The 2026-06-11 investigation reproduced +the crash-free verbs and the spontaneous deaths side by side: coredump +backtraces show a corrupted stack (memory corruption in the musl build), and +no newer image exists upstream. Earlier theories — "native mode is the trigger", +"toggle-read is the trigger" — were timing coincidences; the verbs are sound. + +Operationally: docker mode stays mandatory (=telega-use-docker= = t; the setq +before =(telega t)= is still the right defense), and *every action batch checks +the server first* — =(process-live-p (telega-server--proc))= — restarting via +=(telega t)= when dead and re-checking Ready before firing verbs. A mid-sweep +death is recoverable, not an abort: restart, confirm Ready, resume. Durable-fix +candidates if the crashing gets worse: pin a pre-2026-06 image digest, build +=telega-server= natively against tdlib, or report upstream to zevlg with the +coredumps (=coredumpctl list /usr/bin/telega-server=). Defense in depth: even if the server does die, the scan still works because it reads the cached =telega--chats= hash, not a live query. A dead server is @@ -221,8 +231,29 @@ Actions need the tdlib server *live* (see the SEGFAULT gotcha — a dead server scan-only). All run through telega in the daemon: - reply :: =emacsclient -e "(telega-chat-send-msg (telega-chat-get ) \"\")"= — public-facing (goes out under Craig's name), so run =/voice personal= before sending. Prefer a body file for multi-line. -- mark-read :: =emacsclient -e "(telega-chat--mark-read (telega-chat-get ))"= — clears a specific chat's unread. Never mark the whole account read blindly; the join-notice noise is fine to leave or clear per Craig's call. +- mark-read :: verified 2026-06-11 (the previously documented =telega-chat--mark-read= never existed in telega). The idempotent per-chat verb: + + #+begin_example + emacsclient -e "(let ((chat (telega-chat-get ))) + (telega--viewMessages chat (list (plist-get chat :last_message)) + :source '(:@type \"messageSourceChatList\") :force t) + (telega--readAllChatMentions chat) + (telega--readAllChatReactions chat))" + #+end_example + + =telega-chat-toggle-read= also works but *toggles*: on a chat with zero unread it marks the chat UNREAD, so scripting must guard on =(> (plist-get chat :unread_count) 0)=. Never mark the whole account read blindly; a real DM is handled deliberately, not swept. +- delete-join-notice :: standing policy (Craig, 2026-06-11): a chat whose *newest* message is a =messageContactRegistered= "joined Telegram" notice is a chat Craig never responded to and doesn't want to keep — *delete it* rather than mark it read. The bulk sweep (returns the count deleted): + + #+begin_example + emacsclient -e "(let ((n 0)) + (maphash (lambda (_id chat) + (when (equal (plist-get (plist-get (plist-get chat :last_message) :content) :@type) + \"messageContactRegistered\") + (telega--deleteChatHistory chat t nil) + (setq n (1+ n)))) + telega--chats) + n)" + #+end_example + + =telega--deleteChatHistory chat t nil= removes the chat from the list on Craig's side only (no revoke). First run 2026-06-11 deleted 41 such chats and cut the unread-chat count from 48 to 16. - open :: =emacsclient -e "(telega-chat-with (telega-chat-get ))"= — pop the chat buffer for Craig to read/handle by hand (useful when a real DM needs a considered reply). - -No blanket mark-read verb: the account's unread is mostly join-notices and spam, -and a real DM should be handled deliberately, not swept. diff --git a/claude-templates/.ai/workflows/triage-intake.telegram.org b/claude-templates/.ai/workflows/triage-intake.telegram.org index d855fcf..a318f12 100644 --- a/claude-templates/.ai/workflows/triage-intake.telegram.org +++ b/claude-templates/.ai/workflows/triage-intake.telegram.org @@ -157,14 +157,24 @@ stays non-nil). =telega-server-kill= is what actually stops the server. Call left in =docker ps=. Skipping this whole branch when =TELEGA_WAS_RUNNING= is t is the point of Step 0: never tear down a session Craig is actively using. -⚠ *SEGFAULT GOTCHA — docker mode is the fix.* The tdlib =telega-server= can exit -abnormally (code 139, SIGSEGV) during or just after the initial sync. Observed -on the 2026-06-09 first run; the cure was *docker mode* — =telega-use-docker= = t -with the =zevlg/telega-server:latest= image (already pulled on Craig's box). -Restarted under docker, the server reached "Ready" in ~2s, synced 73 chats, and -held through a live re-scan with no crash. So: *ensure docker mode is on before -the sweep.* If =telega-use-docker= is nil, set it (=(setq telega-use-docker t)=) -and restart telega. +⚠ *SEGFAULT GOTCHA — crashes are spontaneous; treat server death as routine.* +The dockerized =telega-server= (=zevlg/telega-server:latest=, image built +2026-06-04, tdlib 1.8.64) SIGSEGVs (exit 139) *on its own*, minutes-to-hours +into a session — 11 host coredumps between 2026-06-09 and 2026-06-11, several at +times when no triage verb was running. The 2026-06-11 investigation reproduced +the crash-free verbs and the spontaneous deaths side by side: coredump +backtraces show a corrupted stack (memory corruption in the musl build), and +no newer image exists upstream. Earlier theories — "native mode is the trigger", +"toggle-read is the trigger" — were timing coincidences; the verbs are sound. + +Operationally: docker mode stays mandatory (=telega-use-docker= = t; the setq +before =(telega t)= is still the right defense), and *every action batch checks +the server first* — =(process-live-p (telega-server--proc))= — restarting via +=(telega t)= when dead and re-checking Ready before firing verbs. A mid-sweep +death is recoverable, not an abort: restart, confirm Ready, resume. Durable-fix +candidates if the crashing gets worse: pin a pre-2026-06 image digest, build +=telega-server= natively against tdlib, or report upstream to zevlg with the +coredumps (=coredumpctl list /usr/bin/telega-server=). Defense in depth: even if the server does die, the scan still works because it reads the cached =telega--chats= hash, not a live query. A dead server is @@ -221,8 +231,29 @@ Actions need the tdlib server *live* (see the SEGFAULT gotcha — a dead server scan-only). All run through telega in the daemon: - reply :: =emacsclient -e "(telega-chat-send-msg (telega-chat-get ) \"\")"= — public-facing (goes out under Craig's name), so run =/voice personal= before sending. Prefer a body file for multi-line. -- mark-read :: =emacsclient -e "(telega-chat--mark-read (telega-chat-get ))"= — clears a specific chat's unread. Never mark the whole account read blindly; the join-notice noise is fine to leave or clear per Craig's call. +- mark-read :: verified 2026-06-11 (the previously documented =telega-chat--mark-read= never existed in telega). The idempotent per-chat verb: + + #+begin_example + emacsclient -e "(let ((chat (telega-chat-get ))) + (telega--viewMessages chat (list (plist-get chat :last_message)) + :source '(:@type \"messageSourceChatList\") :force t) + (telega--readAllChatMentions chat) + (telega--readAllChatReactions chat))" + #+end_example + + =telega-chat-toggle-read= also works but *toggles*: on a chat with zero unread it marks the chat UNREAD, so scripting must guard on =(> (plist-get chat :unread_count) 0)=. Never mark the whole account read blindly; a real DM is handled deliberately, not swept. +- delete-join-notice :: standing policy (Craig, 2026-06-11): a chat whose *newest* message is a =messageContactRegistered= "joined Telegram" notice is a chat Craig never responded to and doesn't want to keep — *delete it* rather than mark it read. The bulk sweep (returns the count deleted): + + #+begin_example + emacsclient -e "(let ((n 0)) + (maphash (lambda (_id chat) + (when (equal (plist-get (plist-get (plist-get chat :last_message) :content) :@type) + \"messageContactRegistered\") + (telega--deleteChatHistory chat t nil) + (setq n (1+ n)))) + telega--chats) + n)" + #+end_example + + =telega--deleteChatHistory chat t nil= removes the chat from the list on Craig's side only (no revoke). First run 2026-06-11 deleted 41 such chats and cut the unread-chat count from 48 to 16. - open :: =emacsclient -e "(telega-chat-with (telega-chat-get ))"= — pop the chat buffer for Craig to read/handle by hand (useful when a real DM needs a considered reply). - -No blanket mark-read verb: the account's unread is mostly join-notices and spam, -and a real DM should be handled deliberately, not swept. -- cgit v1.2.3