aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--working/triage-telegram-down-launch/note-from-emacsd.txt27
-rw-r--r--working/triage-telegram-down-launch/note-superseded-1723.txt22
-rw-r--r--working/triage-telegram-down-launch/proposed.diff57
-rw-r--r--working/triage-telegram-down-launch/triage-intake.telegram.org.proposed290
-rw-r--r--working/triage-telegram-down-launch/triage-intake.telegram.org.superseded-1723273
-rw-r--r--working/triage-telegram-segfault-rootcause/note-0615-superseded.org33
-rw-r--r--working/triage-telegram-segfault-rootcause/note-0721-correction.org17
-rw-r--r--working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.proposed305
-rw-r--r--working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.superseded-0615295
9 files changed, 0 insertions, 1319 deletions
diff --git a/working/triage-telegram-down-launch/note-from-emacsd.txt b/working/triage-telegram-down-launch/note-from-emacsd.txt
deleted file mode 100644
index 209aa24..0000000
--- a/working/triage-telegram-down-launch/note-from-emacsd.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-FOLLOW-UP / CORRECTION to my earlier triage-intake.telegram.org fix (inbox 2026-07-24-1723). Both affected projects (work + home) replied with reproductions, and the root cause is NOT the missing setq — it's a behavioral bug the plugin's wording allowed. Attaching the re-edited workflow file; please take this version, not the setq-only one. The setq fix is still included and correct; this adds the real fix on top.
-
-WHAT ACTUALLY BIT WORK AND HOME (both independently, same failure)
-
-The telegram source, on finding telega down/unloaded — its NORMAL entry state — reported "SCAN FAILED: telegram — not loaded" (work) or a silent SKIP / blind scan (home), INSTEAD OF running Step 1 to start it. Neither hit the segfault path. Verbatim from work's digest:
-
- ⚠ SCAN FAILED: telegram — telega isn't loaded in the running Emacs daemon, so this sweep is blind on Telegram.
-
-Work then ran the numbered Step 1 verbatim as a reproduction: down → (telega t) → server live (run open listen connect stop) → telega--loadChats → 18 chats. So the recovery IS Step 1; (telega t) both loads the package and starts the docker server. The sessions simply didn't run it — they treated "down" as "failed/skip."
-
-WHY THE WORDING ALLOWED IT
-
-The Quick Reference said "never skips because the server is down" (good), but the closing paragraph said "If any lifecycle step fails (docker image missing, server crash, daemon unreachable), the sweep reports it as SCAN FAILED." An agent conflates "server is down" with "a lifecycle step failed" → SCAN FAILED → blind sweep, without ever attempting the launch. Two agents made exactly this read.
-
-THE FIX (in the attached file)
-
-1. Added a prominent directive right after the Quick Reference intro: DOWN / not-loaded is the TRIGGER to launch, never a reason to skip or fail. (telega t) loads AND starts. SCAN FAILED is reserved for a launch that was ATTEMPTED and did not reach Ready. The :ENABLED: guard tests whether telega is INSTALLED (fboundp), not whether the server is up; a down server never disables the source.
-2. Reworded the closing SCAN FAILED paragraph to say the failure rule applies only AFTER the launch was attempted — a pre-launch down state means "run Step 1," not "SCAN FAILED" — and noted a blind sweep is worse than a clean failure because it hides real unread behind a false all-clear.
-3. Kept the setq fix in Step 1 (latent segfault guard; both projects confirmed it's real but was NOT the cause since the daemon reads telega-use-docker t).
-
-SECONDARY FINDING — ENGINE, not this plugin (your call)
-
-Work reported: "The marker still advanced, which is its own smell — a SCAN FAILED source shouldn't silently advance the sentinel." That's engine behavior in triage-intake.org (the per-source last-run/anchor advance), not the telegram plugin — telegram is :ANCHOR: none, yet something advanced. Worth a look: a source that reports SCAN FAILED advancing its cursor means the next sweep thinks it already covered that window, compounding the blind-sweep hole. I did not touch the engine; flagging for your judgment.
-
-STILL UNRESOLVED, for the record: home carries an older data point (its telega bug task, 2026-07-16) where a launch WITH the setq present returned 'started but server-live-p stayed nil and no container appeared — a launch failure not explained by any of the above. If that's since fixed by the current v1.2.0 image reaching Ready, it's moot; noting it in case the crashing recurs.
-
-No reply needed unless you disagree.
diff --git a/working/triage-telegram-down-launch/note-superseded-1723.txt b/working/triage-telegram-down-launch/note-superseded-1723.txt
deleted file mode 100644
index 7c54ede..0000000
--- a/working/triage-telegram-down-launch/note-superseded-1723.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Fix for a defect in triage-intake.telegram.org: the numbered Step 1 was missing the mandatory `(setq telega-use-docker t)` that the plugin's own Quick Reference and SEGFAULT gotcha both require. Attached is the edited workflow file (.ai/workflows/triage-intake.telegram.org) — please take it into the canonical at claude-templates/.ai/workflows/.
-
-THE DEFECT
-
-The plugin contradicts itself across three places:
-- Quick Reference (line 35): `emacsclient -e "(progn (setq telega-use-docker t) (telega t) 'started)"` — has the setq.
-- SEGFAULT gotcha (line ~170): "docker mode stays mandatory (telega-use-docker = t; the setq before (telega t) is still the right defense)".
-- Numbered Step 1 (lines 91-93): `(progn (unless (...live-p) (telega t)) 'started)` — NO setq.
-
-telega-use-docker defaults to nil. In native (non-docker) mode the dockerized-vs-native tdlib difference is exactly the SEGFAULT the gotcha documents (exit 139). A session following the numbered Step 1 literally, on a daemon where nothing had already forced telega-use-docker to t, starts telega native and crashes the server — which the engine reports as SCAN FAILED at the top of the summary. Both of Craig's personal projects that use the telegram source (work and home) reported triage-intake failing.
-
-THE FIX
-
-Added `(setq telega-use-docker t)` as the first form in Step 1's progn, before the `(unless ... (telega t))`, with a comment pointing at the gotcha. Now Step 1 matches the Quick Reference. Minimal, wording/robustness only; no behavior change on a daemon that already had docker mode on.
-
-IMPORTANT CAVEAT — this is a real defect but NOT a confirmed root cause. I could not reproduce the original failure: Craig doesn't remember the symptom ("it was much earlier"), and his .emacs.d daemon currently reads telega-use-docker t (via .emacs.d's telega-config `:custom`), so on his machine right now the missing setq may have been moot. The fix removes one genuine failure mode that matches the reported symptom; whether it was THE cause is unconfirmed. I've asked work and home for their actual error via their inboxes; if they come back with something else (e.g. the recent :TRIAGE_SOURCES: gating change 4d87f35, or a different source), I'll send a follow-up.
-
-Two things worth your judgment on the canonical:
-1. The stale note at line 37 ("Craig's daemon currently has telega-use-docker nil") is now false on .emacs.d — telega-config sets it t. Consider softening it to "the daemon's default is nil unless an Emacs-config :custom forces it," since the workflow syncs to machines/daemons without that config.
-2. If the daemon reliably has docker mode on everywhere telega runs, this whole class is belt-and-braces — but Step 1 contradicting the Quick Reference is a defect regardless, and the belt is cheap.
-
-No reply needed unless you disagree with the fix.
diff --git a/working/triage-telegram-down-launch/proposed.diff b/working/triage-telegram-down-launch/proposed.diff
deleted file mode 100644
index 72b9cd4..0000000
--- a/working/triage-telegram-down-launch/proposed.diff
+++ /dev/null
@@ -1,57 +0,0 @@
---- claude-templates/.ai/workflows/triage-intake.telegram.org 2026-07-09 13:57:29.819324933 -0500
-+++ working/triage-telegram-down-launch/triage-intake.telegram.org.proposed 2026-07-24 17:26:36.349127827 -0500
-@@ -30,6 +30,20 @@
- unless Craig has Telegram open in Emacs. The scan therefore runs the full
- lifecycle every time, never skips because the server is down:
-
-+⚠ *DOWN / not-loaded is the TRIGGER to launch, never a reason to skip or fail.*
-+This is the exact mistake two projects (work + home, 2026-07-24) made: they
-+probed telega, saw =(telega-server-live-p)= nil or telega not =featurep=, and
-+reported =SCAN FAILED: telegram — not loaded= or a silent SKIP — a *blind*
-+sweep — instead of running Step 1 to start it. A down or unloaded telega is the
-+normal entry state; =(telega t)= both LOADS the package and STARTS the docker
-+server (work confirmed: down → =(telega t)= → Ready, 18 chats). So the plugin
-+MUST run Step 1's launch whenever telega is down/unloaded, wait for Ready, then
-+scan. =SCAN FAILED= is reserved for a launch that was actually ATTEMPTED and did
-+not reach Ready (image missing, server crash on start, daemon unreachable) —
-+never for the pre-launch down state itself. The =:ENABLED:= guard above tests
-+whether telega is INSTALLED (=fboundp=), not whether the server is up; a down
-+server never disables the source.
-+
- 1. Record prior state: TELEGA_WAS_RUNNING via (telega-server-live-p).
- 2. Launch (only if not running):
- emacsclient -e "(progn (setq telega-use-docker t) (telega t) 'started)"
-@@ -48,10 +62,13 @@
- Verify: telega-server-live-p → nil, no zevlg/telega-server container in
- docker ps. If Craig had it running, leave it untouched.
-
--If any lifecycle step fails (docker image missing, server crash, daemon
--unreachable), the sweep reports it as SCAN FAILED at the top of the summary
--per the engine's failure rule — never as a silent skip. Craig gets real
--traffic here.
-+If any lifecycle step fails *after the launch was attempted* (docker image
-+missing, server crash on start, daemon unreachable, Ready never reached), the
-+sweep reports it as SCAN FAILED at the top of the summary per the engine's
-+failure rule — never as a silent skip. This does NOT cover the ordinary
-+pre-launch down state: a down server means "run Step 1," not "SCAN FAILED."
-+Craig gets real traffic here, so a blind sweep that skipped the launch is worse
-+than a clean failure — it hides real unread messages behind a false all-clear.
-
- ** Scan
-
-@@ -88,7 +105,15 @@
- # `(telega t)` starts without popping the root buffer. Docker mode (the stable
- # path — see the SEGFAULT gotcha) reconnects the persisted ~/.telega session in
- # ~2s. Then load the main chat list so telega--chats populates.
-+#
-+# The `(setq telega-use-docker t)` is mandatory and must come BEFORE `(telega t)`:
-+# tdlib segfaults in native mode (SEGFAULT gotcha below), and the daemon's default
-+# is nil unless something (e.g. an Emacs-config :custom) has already forced it. It
-+# was missing here while the Quick Reference and the gotcha both require it —
-+# a session that started telega without it on a native-mode daemon would crash the
-+# server, surfacing as a triage SCAN FAILED. Match the Quick Reference exactly.
- emacsclient -e "(progn
-+ (setq telega-use-docker t)
- (unless (and (fboundp 'telega-server-live-p) (telega-server-live-p)) (telega t))
- 'started)"
- # Poll until Ready with chats synced, or a crash/timeout. Background this with an
diff --git a/working/triage-telegram-down-launch/triage-intake.telegram.org.proposed b/working/triage-telegram-down-launch/triage-intake.telegram.org.proposed
deleted file mode 100644
index 42d46fe..0000000
--- a/working/triage-telegram-down-launch/triage-intake.telegram.org.proposed
+++ /dev/null
@@ -1,290 +0,0 @@
-#+TITLE: Triage Intake — Telegram Source
-#+AUTHOR: Craig Jennings
-#+DATE: 2026-06-09
-
-# Source plugin for the triage-intake engine. See triage-intake.org for the
-# contract and the Phase A-D orchestration. This file declares ONE source.
-#
-# General (personal) source: Telegram via the Emacs telega.el package (tdlib
-# backend). It lives in .ai/workflows/ and is template-synced, sitting with the
-# other general personal sources (personal-gmail, cmail, personal-calendar,
-# signal, github-prs) — not the project plugins. Telegram is personal
-# messaging, not project-specific.
-#
-# Unlike signal-cli (a standalone CLI), Telegram has no headless CLI here. The
-# client is telega.el running inside Craig's long-lived `emacs --daemon`, so the
-# plugin drives it over `emacsclient -e`. tdlib keeps a persisted session in
-# ~/.telega (td.binlog), so a started telega reconnects without re-auth.
-
-* Source: telegram
-:PROPERTIES:
-:ORDER: 24
-:ENABLED: command -v emacsclient && emacsclient -e "(or (featurep 'telega) (fboundp 'telega))" | grep -q t
-:ANCHOR: none
-:SUBAGENT_OVER: 40
-:END:
-
-** Quick reference — full lifecycle
-
-Telega does not autostart with the Emacs daemon. "Down" is its normal state
-unless Craig has Telegram open in Emacs. The scan therefore runs the full
-lifecycle every time, never skips because the server is down:
-
-⚠ *DOWN / not-loaded is the TRIGGER to launch, never a reason to skip or fail.*
-This is the exact mistake two projects (work + home, 2026-07-24) made: they
-probed telega, saw =(telega-server-live-p)= nil or telega not =featurep=, and
-reported =SCAN FAILED: telegram — not loaded= or a silent SKIP — a *blind*
-sweep — instead of running Step 1 to start it. A down or unloaded telega is the
-normal entry state; =(telega t)= both LOADS the package and STARTS the docker
-server (work confirmed: down → =(telega t)= → Ready, 18 chats). So the plugin
-MUST run Step 1's launch whenever telega is down/unloaded, wait for Ready, then
-scan. =SCAN FAILED= is reserved for a launch that was actually ATTEMPTED and did
-not reach Ready (image missing, server crash on start, daemon unreachable) —
-never for the pre-launch down state itself. The =:ENABLED:= guard above tests
-whether telega is INSTALLED (=fboundp=), not whether the server is up; a down
-server never disables the source.
-
-1. Record prior state: TELEGA_WAS_RUNNING via (telega-server-live-p).
-2. Launch (only if not running):
- emacsclient -e "(progn (setq telega-use-docker t) (telega t) 'started)"
- The setq is mandatory defense: tdlib segfaults outside docker mode
- (2026-06-09), and Craig's daemon currently has telega-use-docker nil.
- Wait ~2s for Ready, then (telega--loadChats 'main) until telega--chats
- is populated.
-3. Check messages: the maphash unread scan in ** Scan Step 2 (filters the
- messageContactRegistered join-notice noise).
-4. Send (needs the server live; /voice personal first — Telegram
- occasionally carries WORK communication to Kostya and Vrezh, so treat
- sends with the same care as Slack):
- emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"
-5. Shutdown (ONLY if step 1 recorded not-running):
- emacsclient -e "(progn (telega-server-kill) (ignore-errors (telega-kill t)) 'stopped)"
- Verify: telega-server-live-p → nil, no zevlg/telega-server container in
- docker ps. If Craig had it running, leave it untouched.
-
-If any lifecycle step fails *after the launch was attempted* (docker image
-missing, server crash on start, daemon unreachable, Ready never reached), the
-sweep reports it as SCAN FAILED at the top of the summary per the engine's
-failure rule — never as a silent skip. This does NOT cover the ordinary
-pre-launch down state: a down server means "run Step 1," not "SCAN FAILED."
-Craig gets real traffic here, so a blind sweep that skipped the launch is worse
-than a clean failure — it hides real unread messages behind a false all-clear.
-
-** Scan
-
-Telegram direct messages and groups via telega.el in the running Emacs daemon.
-=ANCHOR: none= because telega reports live unread *state* (each chat's
-=:unread_count=), not a since-window — the engine substitutes no cutoff. Phase B
-uses each message's timestamp only to order and label recency.
-
-The scan reads the =telega--chats= hash table (chat-id → chat plist), which
-telega populates as chats sync. *This is robust to the tdlib server crashing
-mid-session* (see the SEGFAULT gotcha below): the hash retains the last-synced
-unread counts and =:last_message= even after the server dies, so a scan reading
-the hash still returns the most recent known state.
-
-*** Leave-no-trace lifecycle (start only if needed, shut down only if we started it)
-
-telega is a long-lived client inside Craig's daemon. If he already has it
-running, the scan must leave it running. If it's *not* running, the scan starts
-it for the read and shuts it down cleanly afterward, restoring the daemon to its
-prior state. The discipline: *record the prior liveness, branch on it at the
-end.*
-
-*** Step 0 — record prior state
-
-#+begin_src bash
-# t if telega's tdlib server was ALREADY live before this scan, nil otherwise.
-# Hold this value; Step 3 reads it to decide whether to shut telega down.
-TELEGA_WAS_RUNNING=$(emacsclient -e "(and (fboundp 'telega-server-live-p) (telega-server-live-p) t)" 2>/dev/null)
-#+end_src
-
-*** Step 1 — start (docker mode) if not already running, wait for Ready
-
-#+begin_src bash
-# `(telega t)` starts without popping the root buffer. Docker mode (the stable
-# path — see the SEGFAULT gotcha) reconnects the persisted ~/.telega session in
-# ~2s. Then load the main chat list so telega--chats populates.
-#
-# The `(setq telega-use-docker t)` is mandatory and must come BEFORE `(telega t)`:
-# tdlib segfaults in native mode (SEGFAULT gotcha below), and the daemon's default
-# is nil unless something (e.g. an Emacs-config :custom) has already forced it. It
-# was missing here while the Quick Reference and the gotcha both require it —
-# a session that started telega without it on a native-mode daemon would crash the
-# server, surfacing as a triage SCAN FAILED. Match the Quick Reference exactly.
-emacsclient -e "(progn
- (setq telega-use-docker t)
- (unless (and (fboundp 'telega-server-live-p) (telega-server-live-p)) (telega t))
- 'started)"
-# Poll until Ready with chats synced, or a crash/timeout. Background this with an
-# until-loop so the wait doesn't block; exit on Ready-with-chats OR an abnormal
-# server exit. Then force a chat-list load if the hash is thin:
-emacsclient -e "(progn (ignore-errors (telega--loadChats 'main)) (ignore-errors (telega--loadChats 'main)) 'loaded)"
-#+end_src
-
-On a persisted session telega reaches status "Ready" within ~2s; the chat list
-loads over a few more. If =(hash-table-count telega--chats)= is 0 or thin,
-re-issue =telega--loadChats= and poll until it stabilizes.
-
-*** Step 2 — read unread, classified by last-message type
-
-The single most important filter: =messageContactRegistered=. Telegram counts a
-"<name> joined Telegram" service notice as one unread message, so every contact
-from Craig's old address book who ever joined shows as a 1-unread "DM" *that
-person never actually sent*. On the 2026-06-09 first scan this was 30 of ~50
-unread chats. Drop them entirely (tally only).
-
-#+begin_src bash
-emacsclient -e "(let (real svc other)
- (when (boundp 'telega--chats)
- (maphash (lambda (id chat)
- (let* ((uc (or (plist-get chat :unread_count) 0))
- (lm (plist-get chat :last_message))
- (ctype (when lm (plist-get (plist-get lm :content) :@type)))
- (title (or (ignore-errors (substring-no-properties (telega-chat-title chat))) \"?\")))
- (when (> uc 0)
- (cond
- ((equal ctype \"messageContactRegistered\") (push title svc))
- ((member ctype '(\"messageText\" \"messagePhoto\" \"messageVideo\" \"messageDocument\" \"messageVoiceNote\" \"messageSticker\" \"messageAnimation\"))
- (push (list title uc ctype) real))
- (t (push (list title uc (or ctype \"nil\")) other))))))
- telega--chats))
- (list (cons 'real (nreverse real))
- (cons 'joined-telegram-count (length svc))
- (cons 'other (nreverse other))))"
-#+end_src
-
-For a chat that survives as Action-worthy, pull the last message's text to
-classify and summarize:
-
-#+begin_src bash
-# <CHAT-ID> from the maphash key (the scan can also return ids alongside titles)
-emacsclient -e "(let ((c (gethash <CHAT-ID> telega--chats)))
- (substring-no-properties
- (or (telega--tl-get c :last_message :content :text :text) \"\")))"
-#+end_src
-
-*** Step 3 — restore prior state (shut down only if we started it)
-
-#+begin_src bash
-# If telega was NOT running before this scan, shut it down cleanly to leave the
-# daemon as we found it. If Craig already had it running, leave it alone.
-if [ "$TELEGA_WAS_RUNNING" != "t" ]; then
- emacsclient -e "(progn (ignore-errors (telega-server-kill)) (ignore-errors (telega-kill t)) 'killed)"
-fi
-#+end_src
-
-⚠ *In docker mode, =telega-kill= alone is not enough.* =telega-kill= buries the
-telega buffers but leaves the dockerized tdlib server running (=telega-server-live-p=
-stays non-nil). =telega-server-kill= is what actually stops the server. Call
-*both* — server-kill then kill — for a clean teardown. Verified clean afterward:
-=telega-server-live-p= → nil, root buffer gone, no =zevlg/telega-server= container
-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 — 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
-*scan-only* — you can still report unread state, but cannot read new bodies, mark
-read, or reply until it restarts. Treat that as "scan-only, no actions this run"
-and say so.
-
-** Classify
-
-Bias: Craig's personal Telegram is *spam-dominated* with a thin layer of real
-signal. The opposite of Signal (high signal/low volume) — here the volume is
-high and almost all noise. Filter aggressively; surface only the few real
-threads. Kostya and Vrezh occasionally reach Craig here, so a real DM from a
-work contact is Action, full stop.
-
-- *Noise-trash (tally only, never itemized):*
- - =messageContactRegistered= "joined Telegram" notices — always noise, no
- matter whose name is on them. The real contacts Craig knows live here; a
- join notice is not a message from them.
- - Romance/crypto spam DMs — the signature is an emoji-laden handle or a
- two-word "RealName + FantasyWord" suffix (=Gayle ⚾🤎RoyalVineyard=,
- =Cherie🌷🏰 InfiniteRhapsody=, =Jane 🍒🔥=, =Luna Skye=). One unread,
- unsolicited, no prior thread.
- - =Deleted Account-NNNN= threads, blank-title chats, bot channels
- (=Z-Library Official=), Telegram's own =✔️Telegram= service notices.
-- *Noise-keep (never reported):* unread in dev-community groups Craig follows —
- =GNU Emacs=, =zed=, =Kitty=, and similar. Skipped in sweep reports entirely —
- not even a name + count line — unless Craig specifically asks about them
- (Craig's ruling, 2026-06-11, via the work project's handoff). Leave them
- unread; they're reading material, not signal.
-- *Action:* a real text/voice/media message from a *known personal contact* in
- an existing one-to-one thread — an explicit ask, a question, a reply owed. On
- a spam-heavy account these are rare; when one appears, surface it prominently
- with the sender + gist, because it's the needle in the haystack.
-
-The 2026-06-09 calibration run: 30 join-notices + ~10 spam/deleted/bot + 3 dev
-groups + 0 real personal DMs. Expect most sweeps to look like this — a clean
-"nothing real" is the common, correct result.
-
-** Render
-
-#+begin_example
-**Telegram — N unread chats (M real after filtering).** <one-line summary>
-- Action: <real DMs from known contacts, sender + gist, reply owed called out>
-- Noise: K joined-Telegram notices, J spam/bot/deleted (tally only)
-#+end_example
-
-Dev-community group traffic never appears here — no FYI line, no name + count —
-unless Craig asks for it in that sweep (2026-06-11 ruling). Real DMs from known
-contacts still surface as Action.
-
-Omit the block entirely when there's nothing but group traffic, join-notices,
-and spam — under the engine's deltas-only rule that's a no-change source. Render
-the block only when there's an Action item or a Noise tally worth a state-change
-suggestion (e.g. a trash batch).
-
-** Actions
-
-Actions need the tdlib server *live* (see the SEGFAULT gotcha — a dead server is
-scan-only). All run through telega in the daemon:
-
-- reply :: =emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"= — public-facing (goes out under Craig's name), so run =/voice personal= before sending. Prefer a body file for multi-line.
-- 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 <CHAT-ID>)))
- (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 <CHAT-ID>))"= — pop the chat buffer for Craig to read/handle by hand (useful when a real DM needs a considered reply).
diff --git a/working/triage-telegram-down-launch/triage-intake.telegram.org.superseded-1723 b/working/triage-telegram-down-launch/triage-intake.telegram.org.superseded-1723
deleted file mode 100644
index 497f74b..0000000
--- a/working/triage-telegram-down-launch/triage-intake.telegram.org.superseded-1723
+++ /dev/null
@@ -1,273 +0,0 @@
-#+TITLE: Triage Intake — Telegram Source
-#+AUTHOR: Craig Jennings
-#+DATE: 2026-06-09
-
-# Source plugin for the triage-intake engine. See triage-intake.org for the
-# contract and the Phase A-D orchestration. This file declares ONE source.
-#
-# General (personal) source: Telegram via the Emacs telega.el package (tdlib
-# backend). It lives in .ai/workflows/ and is template-synced, sitting with the
-# other general personal sources (personal-gmail, cmail, personal-calendar,
-# signal, github-prs) — not the project plugins. Telegram is personal
-# messaging, not project-specific.
-#
-# Unlike signal-cli (a standalone CLI), Telegram has no headless CLI here. The
-# client is telega.el running inside Craig's long-lived `emacs --daemon`, so the
-# plugin drives it over `emacsclient -e`. tdlib keeps a persisted session in
-# ~/.telega (td.binlog), so a started telega reconnects without re-auth.
-
-* Source: telegram
-:PROPERTIES:
-:ORDER: 24
-:ENABLED: command -v emacsclient && emacsclient -e "(or (featurep 'telega) (fboundp 'telega))" | grep -q t
-:ANCHOR: none
-:SUBAGENT_OVER: 40
-:END:
-
-** Quick reference — full lifecycle
-
-Telega does not autostart with the Emacs daemon. "Down" is its normal state
-unless Craig has Telegram open in Emacs. The scan therefore runs the full
-lifecycle every time, never skips because the server is down:
-
-1. Record prior state: TELEGA_WAS_RUNNING via (telega-server-live-p).
-2. Launch (only if not running):
- emacsclient -e "(progn (setq telega-use-docker t) (telega t) 'started)"
- The setq is mandatory defense: tdlib segfaults outside docker mode
- (2026-06-09), and Craig's daemon currently has telega-use-docker nil.
- Wait ~2s for Ready, then (telega--loadChats 'main) until telega--chats
- is populated.
-3. Check messages: the maphash unread scan in ** Scan Step 2 (filters the
- messageContactRegistered join-notice noise).
-4. Send (needs the server live; /voice personal first — Telegram
- occasionally carries WORK communication to Kostya and Vrezh, so treat
- sends with the same care as Slack):
- emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"
-5. Shutdown (ONLY if step 1 recorded not-running):
- emacsclient -e "(progn (telega-server-kill) (ignore-errors (telega-kill t)) 'stopped)"
- Verify: telega-server-live-p → nil, no zevlg/telega-server container in
- docker ps. If Craig had it running, leave it untouched.
-
-If any lifecycle step fails (docker image missing, server crash, daemon
-unreachable), the sweep reports it as SCAN FAILED at the top of the summary
-per the engine's failure rule — never as a silent skip. Craig gets real
-traffic here.
-
-** Scan
-
-Telegram direct messages and groups via telega.el in the running Emacs daemon.
-=ANCHOR: none= because telega reports live unread *state* (each chat's
-=:unread_count=), not a since-window — the engine substitutes no cutoff. Phase B
-uses each message's timestamp only to order and label recency.
-
-The scan reads the =telega--chats= hash table (chat-id → chat plist), which
-telega populates as chats sync. *This is robust to the tdlib server crashing
-mid-session* (see the SEGFAULT gotcha below): the hash retains the last-synced
-unread counts and =:last_message= even after the server dies, so a scan reading
-the hash still returns the most recent known state.
-
-*** Leave-no-trace lifecycle (start only if needed, shut down only if we started it)
-
-telega is a long-lived client inside Craig's daemon. If he already has it
-running, the scan must leave it running. If it's *not* running, the scan starts
-it for the read and shuts it down cleanly afterward, restoring the daemon to its
-prior state. The discipline: *record the prior liveness, branch on it at the
-end.*
-
-*** Step 0 — record prior state
-
-#+begin_src bash
-# t if telega's tdlib server was ALREADY live before this scan, nil otherwise.
-# Hold this value; Step 3 reads it to decide whether to shut telega down.
-TELEGA_WAS_RUNNING=$(emacsclient -e "(and (fboundp 'telega-server-live-p) (telega-server-live-p) t)" 2>/dev/null)
-#+end_src
-
-*** Step 1 — start (docker mode) if not already running, wait for Ready
-
-#+begin_src bash
-# `(telega t)` starts without popping the root buffer. Docker mode (the stable
-# path — see the SEGFAULT gotcha) reconnects the persisted ~/.telega session in
-# ~2s. Then load the main chat list so telega--chats populates.
-#
-# The `(setq telega-use-docker t)` is mandatory and must come BEFORE `(telega t)`:
-# tdlib segfaults in native mode (SEGFAULT gotcha below), and the daemon's default
-# is nil unless something (e.g. an Emacs-config :custom) has already forced it. It
-# was missing here while the Quick Reference and the gotcha both require it —
-# a session that started telega without it on a native-mode daemon would crash the
-# server, surfacing as a triage SCAN FAILED. Match the Quick Reference exactly.
-emacsclient -e "(progn
- (setq telega-use-docker t)
- (unless (and (fboundp 'telega-server-live-p) (telega-server-live-p)) (telega t))
- 'started)"
-# Poll until Ready with chats synced, or a crash/timeout. Background this with an
-# until-loop so the wait doesn't block; exit on Ready-with-chats OR an abnormal
-# server exit. Then force a chat-list load if the hash is thin:
-emacsclient -e "(progn (ignore-errors (telega--loadChats 'main)) (ignore-errors (telega--loadChats 'main)) 'loaded)"
-#+end_src
-
-On a persisted session telega reaches status "Ready" within ~2s; the chat list
-loads over a few more. If =(hash-table-count telega--chats)= is 0 or thin,
-re-issue =telega--loadChats= and poll until it stabilizes.
-
-*** Step 2 — read unread, classified by last-message type
-
-The single most important filter: =messageContactRegistered=. Telegram counts a
-"<name> joined Telegram" service notice as one unread message, so every contact
-from Craig's old address book who ever joined shows as a 1-unread "DM" *that
-person never actually sent*. On the 2026-06-09 first scan this was 30 of ~50
-unread chats. Drop them entirely (tally only).
-
-#+begin_src bash
-emacsclient -e "(let (real svc other)
- (when (boundp 'telega--chats)
- (maphash (lambda (id chat)
- (let* ((uc (or (plist-get chat :unread_count) 0))
- (lm (plist-get chat :last_message))
- (ctype (when lm (plist-get (plist-get lm :content) :@type)))
- (title (or (ignore-errors (substring-no-properties (telega-chat-title chat))) \"?\")))
- (when (> uc 0)
- (cond
- ((equal ctype \"messageContactRegistered\") (push title svc))
- ((member ctype '(\"messageText\" \"messagePhoto\" \"messageVideo\" \"messageDocument\" \"messageVoiceNote\" \"messageSticker\" \"messageAnimation\"))
- (push (list title uc ctype) real))
- (t (push (list title uc (or ctype \"nil\")) other))))))
- telega--chats))
- (list (cons 'real (nreverse real))
- (cons 'joined-telegram-count (length svc))
- (cons 'other (nreverse other))))"
-#+end_src
-
-For a chat that survives as Action-worthy, pull the last message's text to
-classify and summarize:
-
-#+begin_src bash
-# <CHAT-ID> from the maphash key (the scan can also return ids alongside titles)
-emacsclient -e "(let ((c (gethash <CHAT-ID> telega--chats)))
- (substring-no-properties
- (or (telega--tl-get c :last_message :content :text :text) \"\")))"
-#+end_src
-
-*** Step 3 — restore prior state (shut down only if we started it)
-
-#+begin_src bash
-# If telega was NOT running before this scan, shut it down cleanly to leave the
-# daemon as we found it. If Craig already had it running, leave it alone.
-if [ "$TELEGA_WAS_RUNNING" != "t" ]; then
- emacsclient -e "(progn (ignore-errors (telega-server-kill)) (ignore-errors (telega-kill t)) 'killed)"
-fi
-#+end_src
-
-⚠ *In docker mode, =telega-kill= alone is not enough.* =telega-kill= buries the
-telega buffers but leaves the dockerized tdlib server running (=telega-server-live-p=
-stays non-nil). =telega-server-kill= is what actually stops the server. Call
-*both* — server-kill then kill — for a clean teardown. Verified clean afterward:
-=telega-server-live-p= → nil, root buffer gone, no =zevlg/telega-server= container
-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 — 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
-*scan-only* — you can still report unread state, but cannot read new bodies, mark
-read, or reply until it restarts. Treat that as "scan-only, no actions this run"
-and say so.
-
-** Classify
-
-Bias: Craig's personal Telegram is *spam-dominated* with a thin layer of real
-signal. The opposite of Signal (high signal/low volume) — here the volume is
-high and almost all noise. Filter aggressively; surface only the few real
-threads. Kostya and Vrezh occasionally reach Craig here, so a real DM from a
-work contact is Action, full stop.
-
-- *Noise-trash (tally only, never itemized):*
- - =messageContactRegistered= "joined Telegram" notices — always noise, no
- matter whose name is on them. The real contacts Craig knows live here; a
- join notice is not a message from them.
- - Romance/crypto spam DMs — the signature is an emoji-laden handle or a
- two-word "RealName + FantasyWord" suffix (=Gayle ⚾🤎RoyalVineyard=,
- =Cherie🌷🏰 InfiniteRhapsody=, =Jane 🍒🔥=, =Luna Skye=). One unread,
- unsolicited, no prior thread.
- - =Deleted Account-NNNN= threads, blank-title chats, bot channels
- (=Z-Library Official=), Telegram's own =✔️Telegram= service notices.
-- *Noise-keep (never reported):* unread in dev-community groups Craig follows —
- =GNU Emacs=, =zed=, =Kitty=, and similar. Skipped in sweep reports entirely —
- not even a name + count line — unless Craig specifically asks about them
- (Craig's ruling, 2026-06-11, via the work project's handoff). Leave them
- unread; they're reading material, not signal.
-- *Action:* a real text/voice/media message from a *known personal contact* in
- an existing one-to-one thread — an explicit ask, a question, a reply owed. On
- a spam-heavy account these are rare; when one appears, surface it prominently
- with the sender + gist, because it's the needle in the haystack.
-
-The 2026-06-09 calibration run: 30 join-notices + ~10 spam/deleted/bot + 3 dev
-groups + 0 real personal DMs. Expect most sweeps to look like this — a clean
-"nothing real" is the common, correct result.
-
-** Render
-
-#+begin_example
-**Telegram — N unread chats (M real after filtering).** <one-line summary>
-- Action: <real DMs from known contacts, sender + gist, reply owed called out>
-- Noise: K joined-Telegram notices, J spam/bot/deleted (tally only)
-#+end_example
-
-Dev-community group traffic never appears here — no FYI line, no name + count —
-unless Craig asks for it in that sweep (2026-06-11 ruling). Real DMs from known
-contacts still surface as Action.
-
-Omit the block entirely when there's nothing but group traffic, join-notices,
-and spam — under the engine's deltas-only rule that's a no-change source. Render
-the block only when there's an Action item or a Noise tally worth a state-change
-suggestion (e.g. a trash batch).
-
-** Actions
-
-Actions need the tdlib server *live* (see the SEGFAULT gotcha — a dead server is
-scan-only). All run through telega in the daemon:
-
-- reply :: =emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"= — public-facing (goes out under Craig's name), so run =/voice personal= before sending. Prefer a body file for multi-line.
-- 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 <CHAT-ID>)))
- (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 <CHAT-ID>))"= — pop the chat buffer for Craig to read/handle by hand (useful when a real DM needs a considered reply).
diff --git a/working/triage-telegram-segfault-rootcause/note-0615-superseded.org b/working/triage-telegram-segfault-rootcause/note-0615-superseded.org
deleted file mode 100644
index d948d07..0000000
--- a/working/triage-telegram-segfault-rootcause/note-0615-superseded.org
+++ /dev/null
@@ -1,33 +0,0 @@
-#+TITLE: Intro note for the triage-intake.telegram.org file sent alon
-#+SOURCE: from .emacs.d
-#+DATE: 2026-07-28 06:15:25 -0500
-
-Intro note for the triage-intake.telegram.org file sent alongside this.
-
-WHAT CHANGED
-
-Step 1 called (telega--loadChats 'main). That symbol kills telega-server outright. Fixed to (telega--loadChats '(:@type "chatListMain")) at both call sites, and the SEGFAULT GOTCHA section is rewritten, because it documented the wrong cause and actively misdirected later investigations.
-
-THE CHAIN
-
-telega--loadChats is a raw TL wrapper. It drops its argument into the request as :chat_list with no conversion. telega-server--send prin1's the plist, and telega--tl-pack passes atoms through untouched, so the symbol goes out bare as main. The C parser in server/telega-dat.c, tdat_plist_value, accepts only ( [ " - digit t : or n to start a value. It hits m, prints 'Unexpected char m in plist value', and calls assert(false), which aborts the process. The m in the error is the first character of main.
-
-The symbol shorthand is real but lives in a different layer. telega-filter.el and telega-folders.el convert (eq cl-fspec 'main) into '(:@type "chatListMain"). The raw TL layer never does. telega's own callers always pass the object.
-
-PROVED, NOT INFERRED
-
-From a live Ready server, (telega--loadChats 'main) killed it within seconds and added one coredump with that exact assertion. A restart plus the corrected call survived three consecutive loads with no new coredump and no assertion.
-
-WHY THE OLD GOTCHA MATTERS
-
-It recorded the deaths as spontaneous musl memory corruption and stated 'the verbs are sound'. That is what sent later work at the docker image and tdlib versions rather than at this file. The corrupted stack in the coredump backtraces is what an assert abort looks like; it was read as independent evidence of a memory bug. Two separate investigations lost time to it, and .emacs.d shipped an image pin last night that was aimed at the wrong target.
-
-The rewritten section keeps a caveat: if crashes ever appear with no triage verb running, that is a genuinely separate cause and needs its own investigation rather than reuse of the old story.
-
-BLAST RADIUS
-
-Every project running telegram triage from this plugin has been killing telega-server on every sweep. Work reported it as intermittent Telegram coverage loss, their scan stalling at 19 chats of roughly 50; that stall is this bug seen from outside, since the initial sync populates the hash and then the bad loadChats kills the server before more arrive. I am notifying work directly so they can patch locally before their next sync.
-
-FOLLOW-UP WORTH CONSIDERING
-
-A general rule for the plugin family, or a lint: any argument handed to a telega--* TL wrapper must be a TL object or a plain string, number, or list, never a bare symbol. The failure mode is a hard process abort rather than an elisp error, so ignore-errors around such a call catches nothing, which is exactly why this hid for so long.
diff --git a/working/triage-telegram-segfault-rootcause/note-0721-correction.org b/working/triage-telegram-segfault-rootcause/note-0721-correction.org
deleted file mode 100644
index 73e9274..0000000
--- a/working/triage-telegram-segfault-rootcause/note-0721-correction.org
+++ /dev/null
@@ -1,17 +0,0 @@
-#+TITLE: CORRECTION to the triage-intake.telegram.org write-up sent a
-#+SOURCE: from .emacs.d
-#+DATE: 2026-07-28 07:21:45 -0500
-
-CORRECTION to the triage-intake.telegram.org write-up sent at 06:15 — supersedes that file with the one just sent.
-
-The root cause and the fix are unchanged and confirmed. Work patched both call sites independently and verified at the wire level: the corrected '(:@type "chatListMain") form survived five consecutive telega--loadChats calls, four on chatListMain and one on chatListArchive, server live at every checkpoint. The bare-symbol form died on the first call.
-
-What was wrong is a secondary claim I made in the gotcha rewrite: that the reported '19 chats of ~50' was truncation caused by this bug. It was not. Work measured 19 before the first load and 19 after all five. Nineteen is the real size of that account, so the overnight sweeps that reported a quiet 19-chat account were accurate readings and nothing was missed.
-
-My own control run had already shown this and I wrote the claim anyway. Three consecutive corrected loads held at 19 here too. If the bad call had been truncating, the corrected call would have loaded more; it did not. I treated a coincidence of numbers as a shared mechanism.
-
-Why it matters enough to send a second file: the truncation framing implies a silent-data-loss mode this evidence does not support. This crash kills a scan outright rather than quietly shortening it, which is a different and less insidious failure. Left standing in the canonical it would have taught every future reader to distrust short chat lists for the wrong reason.
-
-The replacement paragraph states the correction, cites work's measurement, and keeps the still-valid point that ignore-errors catches nothing here because the failure is process death rather than an elisp signal.
-
-Please use the file sent immediately after this note rather than the 06:15 one.
diff --git a/working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.proposed b/working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.proposed
deleted file mode 100644
index f4e415d..0000000
--- a/working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.proposed
+++ /dev/null
@@ -1,305 +0,0 @@
-#+TITLE: Triage Intake — Telegram Source
-#+AUTHOR: Craig Jennings
-#+DATE: 2026-06-09
-
-# Source plugin for the triage-intake engine. See triage-intake.org for the
-# contract and the Phase A-D orchestration. This file declares ONE source.
-#
-# General (personal) source: Telegram via the Emacs telega.el package (tdlib
-# backend). It lives in .ai/workflows/ and is template-synced, sitting with the
-# other general personal sources (personal-gmail, cmail, personal-calendar,
-# signal, github-prs) — not the project plugins. Telegram is personal
-# messaging, not project-specific.
-#
-# Unlike signal-cli (a standalone CLI), Telegram has no headless CLI here. The
-# client is telega.el running inside Craig's long-lived `emacs --daemon`, so the
-# plugin drives it over `emacsclient -e`. tdlib keeps a persisted session in
-# ~/.telega (td.binlog), so a started telega reconnects without re-auth.
-
-* Source: telegram
-:PROPERTIES:
-:ORDER: 24
-:ENABLED: command -v emacsclient && emacsclient -e "(or (featurep 'telega) (fboundp 'telega))" | grep -q t
-:ANCHOR: none
-:SUBAGENT_OVER: 40
-:END:
-
-** Quick reference — full lifecycle
-
-Telega does not autostart with the Emacs daemon. "Down" is its normal state
-unless Craig has Telegram open in Emacs. The scan therefore runs the full
-lifecycle every time, never skips because the server is down:
-
-1. Record prior state: TELEGA_WAS_RUNNING via (telega-server-live-p).
-2. Launch (only if not running):
- emacsclient -e "(progn (setq telega-use-docker t) (telega t) 'started)"
- The setq is mandatory defense: tdlib segfaults outside docker mode
- (2026-06-09), and Craig's daemon currently has telega-use-docker nil.
- Wait ~2s for Ready, then (telega--loadChats '(:@type "chatListMain")) until telega--chats
- is populated.
-3. Check messages: the maphash unread scan in ** Scan Step 2 (filters the
- messageContactRegistered join-notice noise).
-4. Send (needs the server live; /voice personal first — Telegram
- occasionally carries WORK communication to Kostya and Vrezh, so treat
- sends with the same care as Slack):
- emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"
-5. Shutdown (ONLY if step 1 recorded not-running):
- emacsclient -e "(progn (telega-server-kill) (ignore-errors (telega-kill t)) 'stopped)"
- Verify: telega-server-live-p → nil, no zevlg/telega-server container in
- docker ps. If Craig had it running, leave it untouched.
-
-If any lifecycle step fails (docker image missing, server crash, daemon
-unreachable), the sweep reports it as SCAN FAILED at the top of the summary
-per the engine's failure rule — never as a silent skip. Craig gets real
-traffic here.
-
-** Scan
-
-Telegram direct messages and groups via telega.el in the running Emacs daemon.
-=ANCHOR: none= because telega reports live unread *state* (each chat's
-=:unread_count=), not a since-window — the engine substitutes no cutoff. Phase B
-uses each message's timestamp only to order and label recency.
-
-The scan reads the =telega--chats= hash table (chat-id → chat plist), which
-telega populates as chats sync. *This is robust to the tdlib server crashing
-mid-session* (see the SEGFAULT gotcha below): the hash retains the last-synced
-unread counts and =:last_message= even after the server dies, so a scan reading
-the hash still returns the most recent known state.
-
-*** Leave-no-trace lifecycle (start only if needed, shut down only if we started it)
-
-telega is a long-lived client inside Craig's daemon. If he already has it
-running, the scan must leave it running. If it's *not* running, the scan starts
-it for the read and shuts it down cleanly afterward, restoring the daemon to its
-prior state. The discipline: *record the prior liveness, branch on it at the
-end.*
-
-*** Step 0 — record prior state
-
-#+begin_src bash
-# t if telega's tdlib server was ALREADY live before this scan, nil otherwise.
-# Hold this value; Step 3 reads it to decide whether to shut telega down.
-TELEGA_WAS_RUNNING=$(emacsclient -e "(and (fboundp 'telega-server-live-p) (telega-server-live-p) t)" 2>/dev/null)
-#+end_src
-
-*** Step 1 — start (docker mode) if not already running, wait for Ready
-
-#+begin_src bash
-# `(telega t)` starts without popping the root buffer. Docker mode (the stable
-# path — see the SEGFAULT gotcha) reconnects the persisted ~/.telega session in
-# ~2s. Then load the main chat list so telega--chats populates.
-emacsclient -e "(progn
- (unless (and (fboundp 'telega-server-live-p) (telega-server-live-p)) (telega t))
- 'started)"
-# Poll until Ready with chats synced, or a crash/timeout. Background this with an
-# until-loop so the wait doesn't block; exit on Ready-with-chats OR an abnormal
-# server exit. Then force a chat-list load if the hash is thin:
-# NOTE: the chat-list argument must be a TL object, not the symbol 'main.
-# `telega--loadChats' puts it straight into the request as :chat_list, and a
-# bare symbol kills the server outright (see the SEGFAULT gotcha below).
-emacsclient -e "(progn (ignore-errors (telega--loadChats '(:@type \"chatListMain\"))) (ignore-errors (telega--loadChats '(:@type \"chatListMain\"))) 'loaded)"
-#+end_src
-
-On a persisted session telega reaches status "Ready" within ~2s; the chat list
-loads over a few more. If =(hash-table-count telega--chats)= is 0 or thin,
-re-issue =telega--loadChats= and poll until it stabilizes.
-
-*** Step 2 — read unread, classified by last-message type
-
-The single most important filter: =messageContactRegistered=. Telegram counts a
-"<name> joined Telegram" service notice as one unread message, so every contact
-from Craig's old address book who ever joined shows as a 1-unread "DM" *that
-person never actually sent*. On the 2026-06-09 first scan this was 30 of ~50
-unread chats. Drop them entirely (tally only).
-
-#+begin_src bash
-emacsclient -e "(let (real svc other)
- (when (boundp 'telega--chats)
- (maphash (lambda (id chat)
- (let* ((uc (or (plist-get chat :unread_count) 0))
- (lm (plist-get chat :last_message))
- (ctype (when lm (plist-get (plist-get lm :content) :@type)))
- (title (or (ignore-errors (substring-no-properties (telega-chat-title chat))) \"?\")))
- (when (> uc 0)
- (cond
- ((equal ctype \"messageContactRegistered\") (push title svc))
- ((member ctype '(\"messageText\" \"messagePhoto\" \"messageVideo\" \"messageDocument\" \"messageVoiceNote\" \"messageSticker\" \"messageAnimation\"))
- (push (list title uc ctype) real))
- (t (push (list title uc (or ctype \"nil\")) other))))))
- telega--chats))
- (list (cons 'real (nreverse real))
- (cons 'joined-telegram-count (length svc))
- (cons 'other (nreverse other))))"
-#+end_src
-
-For a chat that survives as Action-worthy, pull the last message's text to
-classify and summarize:
-
-#+begin_src bash
-# <CHAT-ID> from the maphash key (the scan can also return ids alongside titles)
-emacsclient -e "(let ((c (gethash <CHAT-ID> telega--chats)))
- (substring-no-properties
- (or (telega--tl-get c :last_message :content :text :text) \"\")))"
-#+end_src
-
-*** Step 3 — restore prior state (shut down only if we started it)
-
-#+begin_src bash
-# If telega was NOT running before this scan, shut it down cleanly to leave the
-# daemon as we found it. If Craig already had it running, leave it alone.
-if [ "$TELEGA_WAS_RUNNING" != "t" ]; then
- emacsclient -e "(progn (ignore-errors (telega-server-kill)) (ignore-errors (telega-kill t)) 'killed)"
-fi
-#+end_src
-
-⚠ *In docker mode, =telega-kill= alone is not enough.* =telega-kill= buries the
-telega buffers but leaves the dockerized tdlib server running (=telega-server-live-p=
-stays non-nil). =telega-server-kill= is what actually stops the server. Call
-*both* — server-kill then kill — for a clean teardown. Verified clean afterward:
-=telega-server-live-p= → nil, root buffer gone, no =zevlg/telega-server= container
-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 — this was our bug, not tdlib's. Root-caused 2026-07-28.*
-=telega-server= dies with =Unexpected char 'm' in plist value= followed by
-=Assertion failed: false (telega-dat.c: tdat_plist_value: 500)=. The cause was
-this workflow: Step 1 called =(telega--loadChats 'main)=.
-
-The chain. =telega--loadChats= is a raw TL wrapper — it drops its argument into
-the request as =:chat_list= with no conversion. =telega-server--send= then
-=prin1='s the whole plist, and =telega--tl-pack= passes atoms through untouched,
-so the symbol goes out on the wire bare as =main=. The C parser
-(=server/telega-dat.c=, =tdat_plist_value=) accepts only =(=, =[=, ="=, =-=, a
-digit, =t=, =:=, or =n= to start a value. It hits =m=, prints that line, and
-calls =assert(false)=, which aborts the process. The =m= in the error is
-literally the first character of =main=.
-
-The symbol shorthand is real but belongs to a different layer:
-=telega-filter.el= and =telega-folders.el= convert =(eq cl-fspec 'main)= into
-='(:@type "chatListMain")=. The raw TL layer never does. telega's own callers
-always pass the object (=telega.el:290=, =telega-tdlib-events.el:516=).
-
-Proved by experiment, not inference (2026-07-28): from a live Ready server,
-=(telega--loadChats 'main)= killed it within seconds and added one coredump,
-with that exact assertion; a restart plus =(telega--loadChats '(:@type
-"chatListMain"))= survived three consecutive calls with no new coredump and no
-assertion.
-
-*The previous entry here was wrong and cost real time.* It recorded the deaths
-as spontaneous musl memory corruption and declared "the verbs are sound", which
-sent later investigations at the docker image and tdlib versions instead of at
-this file. The corrupted stack in the backtraces is what an =assert= abort looks
-like, not independent evidence of a memory bug. If crashes are ever seen again
-with *no* triage verb running, that is a genuinely separate cause and needs its
-own investigation — do not reuse the old spontaneous-crash story to explain it.
-
-*This crash kills a scan; it does not silently shorten one.* An earlier draft of
-this section claimed the reported "19 chats of ~50" was truncation caused by the
-bad call. That was wrong, and work disproved it at the wire level on 2026-07-28:
-with the corrected call their count is 19 before the first load and 19 after five
-(four on =chatListMain=, one on =chatListArchive=). Nineteen is the real size of
-that account. The same reading here — 19 stable across three corrected loads —
-was already sitting in the evidence and should have retired the claim before it
-was written down. Treat a short chat list as a real short list unless something
-independently shows the server died mid-sync.
-
-=ignore-errors= around the call never helped — the failure is the server process
-dying, not an elisp signal, so there is nothing for it to catch. That is why the
-death is easy to miss from inside elisp, and why a caller should check
-=(process-live-p (telega-server--proc))= after a load rather than trusting a
-returned value.
-
-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. Any argument
-handed to a =telega--*= TL wrapper must be a TL object or a plain
-string/number/list, never a bare symbol.
-
-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
-*scan-only* — you can still report unread state, but cannot read new bodies, mark
-read, or reply until it restarts. Treat that as "scan-only, no actions this run"
-and say so.
-
-** Classify
-
-Bias: Craig's personal Telegram is *spam-dominated* with a thin layer of real
-signal. The opposite of Signal (high signal/low volume) — here the volume is
-high and almost all noise. Filter aggressively; surface only the few real
-threads. Kostya and Vrezh occasionally reach Craig here, so a real DM from a
-work contact is Action, full stop.
-
-- *Noise-trash (tally only, never itemized):*
- - =messageContactRegistered= "joined Telegram" notices — always noise, no
- matter whose name is on them. The real contacts Craig knows live here; a
- join notice is not a message from them.
- - Romance/crypto spam DMs — the signature is an emoji-laden handle or a
- two-word "RealName + FantasyWord" suffix (=Gayle ⚾🤎RoyalVineyard=,
- =Cherie🌷🏰 InfiniteRhapsody=, =Jane 🍒🔥=, =Luna Skye=). One unread,
- unsolicited, no prior thread.
- - =Deleted Account-NNNN= threads, blank-title chats, bot channels
- (=Z-Library Official=), Telegram's own =✔️Telegram= service notices.
-- *Noise-keep (never reported):* unread in dev-community groups Craig follows —
- =GNU Emacs=, =zed=, =Kitty=, and similar. Skipped in sweep reports entirely —
- not even a name + count line — unless Craig specifically asks about them
- (Craig's ruling, 2026-06-11, via the work project's handoff). Leave them
- unread; they're reading material, not signal.
-- *Action:* a real text/voice/media message from a *known personal contact* in
- an existing one-to-one thread — an explicit ask, a question, a reply owed. On
- a spam-heavy account these are rare; when one appears, surface it prominently
- with the sender + gist, because it's the needle in the haystack.
-
-The 2026-06-09 calibration run: 30 join-notices + ~10 spam/deleted/bot + 3 dev
-groups + 0 real personal DMs. Expect most sweeps to look like this — a clean
-"nothing real" is the common, correct result.
-
-** Render
-
-#+begin_example
-**Telegram — N unread chats (M real after filtering).** <one-line summary>
-- Action: <real DMs from known contacts, sender + gist, reply owed called out>
-- Noise: K joined-Telegram notices, J spam/bot/deleted (tally only)
-#+end_example
-
-Dev-community group traffic never appears here — no FYI line, no name + count —
-unless Craig asks for it in that sweep (2026-06-11 ruling). Real DMs from known
-contacts still surface as Action.
-
-Omit the block entirely when there's nothing but group traffic, join-notices,
-and spam — under the engine's deltas-only rule that's a no-change source. Render
-the block only when there's an Action item or a Noise tally worth a state-change
-suggestion (e.g. a trash batch).
-
-** Actions
-
-Actions need the tdlib server *live* (see the SEGFAULT gotcha — a dead server is
-scan-only). All run through telega in the daemon:
-
-- reply :: =emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"= — public-facing (goes out under Craig's name), so run =/voice personal= before sending. Prefer a body file for multi-line.
-- 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 <CHAT-ID>)))
- (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 <CHAT-ID>))"= — pop the chat buffer for Craig to read/handle by hand (useful when a real DM needs a considered reply).
diff --git a/working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.superseded-0615 b/working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.superseded-0615
deleted file mode 100644
index 74b8aee..0000000
--- a/working/triage-telegram-segfault-rootcause/triage-intake.telegram.org.superseded-0615
+++ /dev/null
@@ -1,295 +0,0 @@
-#+TITLE: Triage Intake — Telegram Source
-#+AUTHOR: Craig Jennings
-#+DATE: 2026-06-09
-
-# Source plugin for the triage-intake engine. See triage-intake.org for the
-# contract and the Phase A-D orchestration. This file declares ONE source.
-#
-# General (personal) source: Telegram via the Emacs telega.el package (tdlib
-# backend). It lives in .ai/workflows/ and is template-synced, sitting with the
-# other general personal sources (personal-gmail, cmail, personal-calendar,
-# signal, github-prs) — not the project plugins. Telegram is personal
-# messaging, not project-specific.
-#
-# Unlike signal-cli (a standalone CLI), Telegram has no headless CLI here. The
-# client is telega.el running inside Craig's long-lived `emacs --daemon`, so the
-# plugin drives it over `emacsclient -e`. tdlib keeps a persisted session in
-# ~/.telega (td.binlog), so a started telega reconnects without re-auth.
-
-* Source: telegram
-:PROPERTIES:
-:ORDER: 24
-:ENABLED: command -v emacsclient && emacsclient -e "(or (featurep 'telega) (fboundp 'telega))" | grep -q t
-:ANCHOR: none
-:SUBAGENT_OVER: 40
-:END:
-
-** Quick reference — full lifecycle
-
-Telega does not autostart with the Emacs daemon. "Down" is its normal state
-unless Craig has Telegram open in Emacs. The scan therefore runs the full
-lifecycle every time, never skips because the server is down:
-
-1. Record prior state: TELEGA_WAS_RUNNING via (telega-server-live-p).
-2. Launch (only if not running):
- emacsclient -e "(progn (setq telega-use-docker t) (telega t) 'started)"
- The setq is mandatory defense: tdlib segfaults outside docker mode
- (2026-06-09), and Craig's daemon currently has telega-use-docker nil.
- Wait ~2s for Ready, then (telega--loadChats '(:@type "chatListMain")) until telega--chats
- is populated.
-3. Check messages: the maphash unread scan in ** Scan Step 2 (filters the
- messageContactRegistered join-notice noise).
-4. Send (needs the server live; /voice personal first — Telegram
- occasionally carries WORK communication to Kostya and Vrezh, so treat
- sends with the same care as Slack):
- emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"
-5. Shutdown (ONLY if step 1 recorded not-running):
- emacsclient -e "(progn (telega-server-kill) (ignore-errors (telega-kill t)) 'stopped)"
- Verify: telega-server-live-p → nil, no zevlg/telega-server container in
- docker ps. If Craig had it running, leave it untouched.
-
-If any lifecycle step fails (docker image missing, server crash, daemon
-unreachable), the sweep reports it as SCAN FAILED at the top of the summary
-per the engine's failure rule — never as a silent skip. Craig gets real
-traffic here.
-
-** Scan
-
-Telegram direct messages and groups via telega.el in the running Emacs daemon.
-=ANCHOR: none= because telega reports live unread *state* (each chat's
-=:unread_count=), not a since-window — the engine substitutes no cutoff. Phase B
-uses each message's timestamp only to order and label recency.
-
-The scan reads the =telega--chats= hash table (chat-id → chat plist), which
-telega populates as chats sync. *This is robust to the tdlib server crashing
-mid-session* (see the SEGFAULT gotcha below): the hash retains the last-synced
-unread counts and =:last_message= even after the server dies, so a scan reading
-the hash still returns the most recent known state.
-
-*** Leave-no-trace lifecycle (start only if needed, shut down only if we started it)
-
-telega is a long-lived client inside Craig's daemon. If he already has it
-running, the scan must leave it running. If it's *not* running, the scan starts
-it for the read and shuts it down cleanly afterward, restoring the daemon to its
-prior state. The discipline: *record the prior liveness, branch on it at the
-end.*
-
-*** Step 0 — record prior state
-
-#+begin_src bash
-# t if telega's tdlib server was ALREADY live before this scan, nil otherwise.
-# Hold this value; Step 3 reads it to decide whether to shut telega down.
-TELEGA_WAS_RUNNING=$(emacsclient -e "(and (fboundp 'telega-server-live-p) (telega-server-live-p) t)" 2>/dev/null)
-#+end_src
-
-*** Step 1 — start (docker mode) if not already running, wait for Ready
-
-#+begin_src bash
-# `(telega t)` starts without popping the root buffer. Docker mode (the stable
-# path — see the SEGFAULT gotcha) reconnects the persisted ~/.telega session in
-# ~2s. Then load the main chat list so telega--chats populates.
-emacsclient -e "(progn
- (unless (and (fboundp 'telega-server-live-p) (telega-server-live-p)) (telega t))
- 'started)"
-# Poll until Ready with chats synced, or a crash/timeout. Background this with an
-# until-loop so the wait doesn't block; exit on Ready-with-chats OR an abnormal
-# server exit. Then force a chat-list load if the hash is thin:
-# NOTE: the chat-list argument must be a TL object, not the symbol 'main.
-# `telega--loadChats' puts it straight into the request as :chat_list, and a
-# bare symbol kills the server outright (see the SEGFAULT gotcha below).
-emacsclient -e "(progn (ignore-errors (telega--loadChats '(:@type \"chatListMain\"))) (ignore-errors (telega--loadChats '(:@type \"chatListMain\"))) 'loaded)"
-#+end_src
-
-On a persisted session telega reaches status "Ready" within ~2s; the chat list
-loads over a few more. If =(hash-table-count telega--chats)= is 0 or thin,
-re-issue =telega--loadChats= and poll until it stabilizes.
-
-*** Step 2 — read unread, classified by last-message type
-
-The single most important filter: =messageContactRegistered=. Telegram counts a
-"<name> joined Telegram" service notice as one unread message, so every contact
-from Craig's old address book who ever joined shows as a 1-unread "DM" *that
-person never actually sent*. On the 2026-06-09 first scan this was 30 of ~50
-unread chats. Drop them entirely (tally only).
-
-#+begin_src bash
-emacsclient -e "(let (real svc other)
- (when (boundp 'telega--chats)
- (maphash (lambda (id chat)
- (let* ((uc (or (plist-get chat :unread_count) 0))
- (lm (plist-get chat :last_message))
- (ctype (when lm (plist-get (plist-get lm :content) :@type)))
- (title (or (ignore-errors (substring-no-properties (telega-chat-title chat))) \"?\")))
- (when (> uc 0)
- (cond
- ((equal ctype \"messageContactRegistered\") (push title svc))
- ((member ctype '(\"messageText\" \"messagePhoto\" \"messageVideo\" \"messageDocument\" \"messageVoiceNote\" \"messageSticker\" \"messageAnimation\"))
- (push (list title uc ctype) real))
- (t (push (list title uc (or ctype \"nil\")) other))))))
- telega--chats))
- (list (cons 'real (nreverse real))
- (cons 'joined-telegram-count (length svc))
- (cons 'other (nreverse other))))"
-#+end_src
-
-For a chat that survives as Action-worthy, pull the last message's text to
-classify and summarize:
-
-#+begin_src bash
-# <CHAT-ID> from the maphash key (the scan can also return ids alongside titles)
-emacsclient -e "(let ((c (gethash <CHAT-ID> telega--chats)))
- (substring-no-properties
- (or (telega--tl-get c :last_message :content :text :text) \"\")))"
-#+end_src
-
-*** Step 3 — restore prior state (shut down only if we started it)
-
-#+begin_src bash
-# If telega was NOT running before this scan, shut it down cleanly to leave the
-# daemon as we found it. If Craig already had it running, leave it alone.
-if [ "$TELEGA_WAS_RUNNING" != "t" ]; then
- emacsclient -e "(progn (ignore-errors (telega-server-kill)) (ignore-errors (telega-kill t)) 'killed)"
-fi
-#+end_src
-
-⚠ *In docker mode, =telega-kill= alone is not enough.* =telega-kill= buries the
-telega buffers but leaves the dockerized tdlib server running (=telega-server-live-p=
-stays non-nil). =telega-server-kill= is what actually stops the server. Call
-*both* — server-kill then kill — for a clean teardown. Verified clean afterward:
-=telega-server-live-p= → nil, root buffer gone, no =zevlg/telega-server= container
-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 — this was our bug, not tdlib's. Root-caused 2026-07-28.*
-=telega-server= dies with =Unexpected char 'm' in plist value= followed by
-=Assertion failed: false (telega-dat.c: tdat_plist_value: 500)=. The cause was
-this workflow: Step 1 called =(telega--loadChats 'main)=.
-
-The chain. =telega--loadChats= is a raw TL wrapper — it drops its argument into
-the request as =:chat_list= with no conversion. =telega-server--send= then
-=prin1='s the whole plist, and =telega--tl-pack= passes atoms through untouched,
-so the symbol goes out on the wire bare as =main=. The C parser
-(=server/telega-dat.c=, =tdat_plist_value=) accepts only =(=, =[=, ="=, =-=, a
-digit, =t=, =:=, or =n= to start a value. It hits =m=, prints that line, and
-calls =assert(false)=, which aborts the process. The =m= in the error is
-literally the first character of =main=.
-
-The symbol shorthand is real but belongs to a different layer:
-=telega-filter.el= and =telega-folders.el= convert =(eq cl-fspec 'main)= into
-='(:@type "chatListMain")=. The raw TL layer never does. telega's own callers
-always pass the object (=telega.el:290=, =telega-tdlib-events.el:516=).
-
-Proved by experiment, not inference (2026-07-28): from a live Ready server,
-=(telega--loadChats 'main)= killed it within seconds and added one coredump,
-with that exact assertion; a restart plus =(telega--loadChats '(:@type
-"chatListMain"))= survived three consecutive calls with no new coredump and no
-assertion.
-
-*The previous entry here was wrong and cost real time.* It recorded the deaths
-as spontaneous musl memory corruption and declared "the verbs are sound", which
-sent later investigations at the docker image and tdlib versions instead of at
-this file. The corrupted stack in the backtraces is what an =assert= abort looks
-like, not independent evidence of a memory bug. If crashes are ever seen again
-with *no* triage verb running, that is a genuinely separate cause and needs its
-own investigation — do not reuse the old spontaneous-crash story to explain it.
-
-The "stalled at 19 of ~50 chats" symptom is the same bug seen from outside:
-the initial sync populates the hash, then the bad =loadChats= kills the server
-before more arrive, so the scan reads a short list that looks like a quiet
-account. =ignore-errors= around the call never helped — the failure is the
-server process dying, not an elisp signal, so there is nothing for it to catch.
-
-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. Any argument
-handed to a =telega--*= TL wrapper must be a TL object or a plain
-string/number/list, never a bare symbol.
-
-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
-*scan-only* — you can still report unread state, but cannot read new bodies, mark
-read, or reply until it restarts. Treat that as "scan-only, no actions this run"
-and say so.
-
-** Classify
-
-Bias: Craig's personal Telegram is *spam-dominated* with a thin layer of real
-signal. The opposite of Signal (high signal/low volume) — here the volume is
-high and almost all noise. Filter aggressively; surface only the few real
-threads. Kostya and Vrezh occasionally reach Craig here, so a real DM from a
-work contact is Action, full stop.
-
-- *Noise-trash (tally only, never itemized):*
- - =messageContactRegistered= "joined Telegram" notices — always noise, no
- matter whose name is on them. The real contacts Craig knows live here; a
- join notice is not a message from them.
- - Romance/crypto spam DMs — the signature is an emoji-laden handle or a
- two-word "RealName + FantasyWord" suffix (=Gayle ⚾🤎RoyalVineyard=,
- =Cherie🌷🏰 InfiniteRhapsody=, =Jane 🍒🔥=, =Luna Skye=). One unread,
- unsolicited, no prior thread.
- - =Deleted Account-NNNN= threads, blank-title chats, bot channels
- (=Z-Library Official=), Telegram's own =✔️Telegram= service notices.
-- *Noise-keep (never reported):* unread in dev-community groups Craig follows —
- =GNU Emacs=, =zed=, =Kitty=, and similar. Skipped in sweep reports entirely —
- not even a name + count line — unless Craig specifically asks about them
- (Craig's ruling, 2026-06-11, via the work project's handoff). Leave them
- unread; they're reading material, not signal.
-- *Action:* a real text/voice/media message from a *known personal contact* in
- an existing one-to-one thread — an explicit ask, a question, a reply owed. On
- a spam-heavy account these are rare; when one appears, surface it prominently
- with the sender + gist, because it's the needle in the haystack.
-
-The 2026-06-09 calibration run: 30 join-notices + ~10 spam/deleted/bot + 3 dev
-groups + 0 real personal DMs. Expect most sweeps to look like this — a clean
-"nothing real" is the common, correct result.
-
-** Render
-
-#+begin_example
-**Telegram — N unread chats (M real after filtering).** <one-line summary>
-- Action: <real DMs from known contacts, sender + gist, reply owed called out>
-- Noise: K joined-Telegram notices, J spam/bot/deleted (tally only)
-#+end_example
-
-Dev-community group traffic never appears here — no FYI line, no name + count —
-unless Craig asks for it in that sweep (2026-06-11 ruling). Real DMs from known
-contacts still surface as Action.
-
-Omit the block entirely when there's nothing but group traffic, join-notices,
-and spam — under the engine's deltas-only rule that's a no-change source. Render
-the block only when there's an Action item or a Noise tally worth a state-change
-suggestion (e.g. a trash batch).
-
-** Actions
-
-Actions need the tdlib server *live* (see the SEGFAULT gotcha — a dead server is
-scan-only). All run through telega in the daemon:
-
-- reply :: =emacsclient -e "(telega-chat-send-msg (telega-chat-get <CHAT-ID>) \"<body>\")"= — public-facing (goes out under Craig's name), so run =/voice personal= before sending. Prefer a body file for multi-line.
-- 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 <CHAT-ID>)))
- (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 <CHAT-ID>))"= — pop the chat buffer for Craig to read/handle by hand (useful when a real DM needs a considered reply).