aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-08-25 06:30:04 -0600
committerCraig Jennings <c@cjennings.net>2026-08-25 06:30:04 -0600
commit91fbac722e83cd108a295542a3c88de66d209fe8 (patch)
treea444b4181cc07416f82930fb7486a1c54ccf1a75
parent9b53d707f5e2dfe5237ae4db338aa65e83d9c6b2 (diff)
downloaddotemacs-91fbac722e83cd108a295542a3c88de66d209fe8.tar.gz
dotemacs-91fbac722e83cd108a295542a3c88de66d209fe8.zip
fix(telega): build the server image locally to meet tdlib 1.8.66HEADmain
MELPA's 2026-08-18 telega raised telega-tdlib-min-version to 1.8.66. The pinned upstream image ships 1.8.64, so telega killed its own server on every launch. Upstream's only 1.8.66 image won't start: it's missing libglycin (zevlg/telega.el#596). docker/telega-server/Dockerfile derives from that upstream image, pinned by digest, and adds the one missing package. make telega-image builds it as cj/telega-server:1.8.66-glycin, cj/telega-docker-image defaults to that tag, and a test holds the two equal. cj/telega now refuses to launch with a make telega-image hint when the image isn't built, since docker would otherwise try to pull a local-only tag from the registry. setup-telega.sh points at the make target instead of M-x telega-server-build. I chose the local build over pinning the telega package back to 20260706.2147. The derived image satisfies both machines' floors. package.el can't pin a version and MELPA serves only its latest build, so nothing would enforce the package pin. When #596 is fixed upstream the pin returns to a registry digest and the Dockerfile goes.
-rw-r--r--Makefile14
-rw-r--r--docker/telega-server/Dockerfile16
-rw-r--r--docs/design/2026-08-23-telega-docker-pin-tdlib-mismatch.org29
-rw-r--r--modules/telega-config.el63
-rwxr-xr-xscripts/setup-telega.sh12
-rw-r--r--tests/test-setup-telega.bats4
-rw-r--r--tests/test-telega-config--docker-pin.el141
-rw-r--r--tests/test-telega-config.el4
8 files changed, 248 insertions, 35 deletions
diff --git a/Makefile b/Makefile
index b8580912..fd759f30 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@
# make compile-file FILE= - Byte-compile one file with the project load path
# make lint - Run all linters (checkdoc, package-lint, elisp-lint)
# make bootstrap - Install every package headlessly (fresh machine)
+# make telega-image - Build the telega-server container image
# make profile - Profile Emacs startup performance
# make clean - Remove test artifacts and compiled files
# make clean-compiled - Remove .elc/.eln files only
@@ -55,7 +56,7 @@ EMACS_TEST = $(EMACS_BATCH) -L $(TEST_DIR) -L $(MODULE_DIR)
.PHONY: help targets test test-all test-unit test-integration test-file test-name \
test-bash theme-studio-test theme-studio-check theme-studio-coverage theme-studio-gen theme-studio-open theme-studio-theme theme-studio-theme-load theme-studio-theme-reload deploy-wip \
benchmark coverage coverage-summary coverage-clean \
- validate-parens validate-modules compile compile-file lint bootstrap profile \
+ validate-parens validate-modules compile compile-file lint bootstrap telega-image profile \
task-sorted \
clean clean-compiled clean-tests reset
@@ -102,6 +103,7 @@ help:
@echo ""
@echo " Utilities:"
@echo " make bootstrap - Install every package headlessly (fresh machine)"
+ @echo " make telega-image - Build the telega-server container image"
@echo " make profile - Profile Emacs startup performance"
@echo " make clean - Remove test artifacts and compiled files"
@echo " make clean-compiled - Remove .elc/.eln files only"
@@ -457,6 +459,16 @@ lint:
bootstrap:
@bash scripts/bootstrap-packages.sh
+# telega-server container image. Built locally from a digest-pinned upstream
+# base plus the library upstream forgot (see docker/telega-server/Dockerfile).
+# The tag here is the source of truth: `cj/telega-docker-image' in
+# modules/telega-config.el must name the same one, and a test holds them equal.
+TELEGA_IMAGE = cj/telega-server:1.8.66-glycin
+
+telega-image:
+ @docker build -t $(TELEGA_IMAGE) docker/telega-server
+ @echo "built $(TELEGA_IMAGE)"
+
profile:
@echo "Profiling Emacs startup..."
@if [ -f "$(EMACS_HOME)/early-init.el" ]; then \
diff --git a/docker/telega-server/Dockerfile b/docker/telega-server/Dockerfile
new file mode 100644
index 00000000..6bd2c579
--- /dev/null
+++ b/docker/telega-server/Dockerfile
@@ -0,0 +1,16 @@
+# telega-server image for modules/telega-config.el. Build with `make telega-image'.
+#
+# Upstream's zevlg/telega-server:latest ships tdlib 1.8.66 (the floor the
+# current telega package demands) but its telega-server binary fails to start:
+# libgdk_pixbuf needs libglycin-2.so.0 and the image doesn't carry it. Known
+# open bug: https://github.com/zevlg/telega.el/issues/596. This derived image
+# adds the one missing package and nothing else.
+#
+# The base is pinned by digest, not tag, so a rebuild on another machine gets
+# the same upstream layers. libglycin itself is deliberately not version
+# pinned: Alpine's repos keep only the current build of a package, so an exact
+# `libglycin=X-rN' fails the build the day it rotates, which is worse than one
+# layer differing between machines. When #596 is fixed upstream, point
+# `cj/telega-docker-image' back at a registry digest and delete this file.
+FROM zevlg/telega-server@sha256:aa7e79c4e9587c84065782df9399653fb1a967078b08ec1ded77738d4c2d70ad
+RUN apk add --no-cache libglycin
diff --git a/docs/design/2026-08-23-telega-docker-pin-tdlib-mismatch.org b/docs/design/2026-08-23-telega-docker-pin-tdlib-mismatch.org
index 919c84b8..6428e8c1 100644
--- a/docs/design/2026-08-23-telega-docker-pin-tdlib-mismatch.org
+++ b/docs/design/2026-08-23-telega-docker-pin-tdlib-mismatch.org
@@ -212,6 +212,35 @@ way it goes:
- A controlled reproduction of the silent death with the daemon watched, to
find out why the armed notification wasn't seen (previous section).
+* Decision (2026-08-25): derived image, built from a Dockerfile in this repo
+
+I went with the derived image, in a form that keeps most of what the digest
+pin gave. Three things settled it:
+
+- The derived image (tdlib 1.8.66) satisfies both machines' current packages:
+ velox's telega wants 1.8.66 and ratio's wants 1.8.64, and telega accepts
+ anything at or above its floor. The package pin would have worked only on
+ velox, and only after copying =elpa/telega-20260706.2147= over from ratio.
+- package.el has no version pin and MELPA serves only its latest build, so
+ "pin the package back" is a copied directory plus the hope that nobody runs
+ =package-upgrade= again. Nothing enforces it.
+- The pre-reinstall backup on the NAS holds velox's =~/.telega= (td.binlog
+ and db.sqlite from 2026-07-05), so a working image plus that directory may
+ restore the session without a phone re-auth.
+
+The shape: =docker/telega-server/Dockerfile= (FROM pinned to the upstream
+digest, plus =apk add libglycin=), =make telega-image= builds it under
+=cj/telega-server:1.8.66-glycin=, =cj/telega-docker-image= defaults to that
+tag, and =cj/telega= refuses to launch with a "run make telega-image" message
+when the image isn't built, so a fresh machine gets a pointer instead of a
+confusing registry pull failure. The digest guarantee lives in the
+Dockerfile's FROM line; a test holds the defcustom default equal to the
+Makefile's tag. When #596 is fixed upstream, the pin goes back to a registry
+digest and the Dockerfile goes.
+
+Not done here: the launch-time tdlib-version assertion, and the controlled
+reproduction of the unseen death notification. Both are filed.
+
* Reproducing it
#+begin_src sh
diff --git a/modules/telega-config.el b/modules/telega-config.el
index 5a20d430..b9b80481 100644
--- a/modules/telega-config.el
+++ b/modules/telega-config.el
@@ -18,9 +18,10 @@
;;
;; TDLib (Telegram Database Library) runs in a docker container via
;; `telega-use-docker' so a fresh-clone install does not need a
-;; system-level TDLib build. =scripts/setup-telega.sh= prepares the
-;; container the first time; afterwards telega.el reattaches
-;; automatically.
+;; system-level TDLib build. The image is built locally from
+;; =docker/telega-server/Dockerfile= with =make telega-image= (see the
+;; pin section below for why it is not pulled from the registry);
+;; =scripts/setup-telega.sh= covers the rest of a fresh clone.
;;
;; First-run auth (phone number + Telegram verification code) is
;; interactive and happens inside `M-x telega'. This module does not
@@ -68,20 +69,29 @@
;; telega picks its container image in `telega-docker--image-name', which only
;; pins to a version tag when `telega-tdlib-min-version' equals
;; `telega-tdlib-max-version' and the version ends in ".0". Here min is
-;; "1.8.64" and max is nil, so that test never passes and the image is always
+;; "1.8.66" and max is nil, so that test never passes and the image is always
;; "zevlg/telega-server:latest" -- a floating tag. The elpa package is fixed
;; at whatever version was installed, so the server can be replaced underneath
;; a static elisp without anything announcing it.
;;
-;; Pinning by digest names one immutable image. Set to nil to hand the choice
-;; back to telega.
+;; The pin used to be a registry digest. It became a local tag on 2026-08-25:
+;; the telega package raised its tdlib floor to 1.8.66, and upstream's only
+;; image at that version fails to start (libglycin missing,
+;; zevlg/telega.el#596). docker/telega-server/Dockerfile derives a working
+;; image from that upstream digest plus the one missing package, and
+;; `make telega-image' builds it under the tag below. The digest guarantee
+;; now lives in the Dockerfile's FROM line; the Makefile owns the tag and a
+;; test holds this default equal to it. Set to nil to hand the choice back
+;; to telega.
(defcustom cj/telega-docker-image
- "zevlg/telega-server@sha256:a4b88e029ba381eca7c37c9618c9e3ad73aa9db2097fe07a0c6684d40d32b84e"
+ "cj/telega-server:1.8.66-glycin"
"Container image reference for `telega-server', or nil for telega's default.
-Pin by digest rather than tag: a tag can be re-pushed upstream, a digest
-cannot. The default is the image carrying libtdjson 1.8.64, which matches
-this telega's `telega-tdlib-min-version'."
+The default names the image `make telega-image' builds locally from
+docker/telega-server/Dockerfile, whose base is pinned by upstream digest.
+It must match TELEGA_IMAGE in the Makefile; `cj/telega' refuses to launch
+when the image is not present, since docker would otherwise try to pull a
+local-only tag from the registry and fail confusingly."
:type '(choice (const :tag "Let telega infer the image" nil)
(string :tag "Image reference"))
:group 'telega-docker)
@@ -175,6 +185,21 @@ into telega's sentinel and abort its status handling and relogin path."
(with-eval-after-load 'telega-server
(advice-add 'telega-server--sentinel :after #'cj/--telega-server-notify-death))
+(defun cj/--telega-docker-image-present-p (image)
+ "Return non-nil when IMAGE exists in the local docker image store.
+Uses `docker image inspect' rather than `docker images': the listing hides
+digest-pulled and untagged images, and this check exists because that
+listing lied once. Any failure (docker absent, daemon down) reads as
+not-present, which routes the user to the same make target."
+ (condition-case nil
+ (zerop (call-process "docker" nil nil nil "image" "inspect" image))
+ (error nil)))
+
+(defun cj/--telega-missing-image-message (image)
+ "Return the user-facing message for a pinned IMAGE that is not built yet."
+ (format "telega-server image %s is not built -- run `make telega-image' in %s"
+ image (abbreviate-file-name user-emacs-directory)))
+
(defun cj/telega ()
"Launch telega.el with a helpful message when it isn't installed yet.
@@ -183,14 +208,22 @@ stale MELPA archive index can't take startup down with a 404. The
trade-off: a fresh clone needs a one-time install before this
launcher works. Without this wrapper, the autoload stub fails with
the cryptic =Cannot open load file: telega=; with it, the user gets
-pointed at =scripts/setup-telega.sh= and the manual fallback."
+pointed at =scripts/setup-telega.sh= and the manual fallback.
+
+When `cj/telega-docker-image' is set, the image must already be built:
+it is a local tag, so a missing one would send docker to the registry
+for something that was never there. The check is skipped with no pin,
+where telega infers and pulls its own image."
(interactive)
- (if (or (featurep 'telega)
- (locate-library "telega"))
- (telega)
+ (unless (or (featurep 'telega)
+ (locate-library "telega"))
(user-error
(concat "telega not installed -- run scripts/setup-telega.sh, "
- "or `M-x package-install RET telega'"))))
+ "or `M-x package-install RET telega'")))
+ (let ((image (cj/--telega-docker-pinned-image)))
+ (when (and image (not (cj/--telega-docker-image-present-p image)))
+ (user-error "%s" (cj/--telega-missing-image-message image))))
+ (telega))
(cj/register-command "T" #'cj/telega)
diff --git a/scripts/setup-telega.sh b/scripts/setup-telega.sh
index 9a8c0d02..351f98ba 100755
--- a/scripts/setup-telega.sh
+++ b/scripts/setup-telega.sh
@@ -7,8 +7,8 @@
# - Verifies docker is installed and the daemon is responsive.
# - Verifies the user can talk to docker without sudo (group membership).
# - Pulls the telega-server image if a public one is configured (env var
-# `TELEGA_DOCKER_IMAGE'); otherwise prints the in-Emacs build command
-# (`M-x telega-server-build') for the user to run once.
+# `TELEGA_DOCKER_IMAGE'); otherwise points at `make telega-image', which
+# builds the pinned image from docker/telega-server/Dockerfile.
# - Installs the `telega' Emacs package via package.el if it isn't
# already in package-user-dir. modules/telega-config.el uses
# `:ensure nil' (a stale MELPA index can 404 and take startup down
@@ -52,10 +52,10 @@ pull_or_announce_image() {
if [[ -z "$TELEGA_DOCKER_IMAGE" ]]; then
cat <<EOF
→ no public image configured (set TELEGA_DOCKER_IMAGE to override)
- build the telega-server image once from inside Emacs:
- M-x telega-server-build
- telega.el handles the docker build under the hood when
- \`telega-use-docker' is t (set in modules/telega-config.el).
+ build the telega-server image once from the repo's Dockerfile:
+ make telega-image
+ modules/telega-config.el pins \`cj/telega-docker-image' to the tag
+ that target builds (docker/telega-server/Dockerfile).
EOF
return 0
fi
diff --git a/tests/test-setup-telega.bats b/tests/test-setup-telega.bats
index 3282b9e1..518ecc7b 100644
--- a/tests/test-setup-telega.bats
+++ b/tests/test-setup-telega.bats
@@ -57,11 +57,11 @@ setup() {
# --------------------------- pull_or_announce_image -----------------------
-@test "pull_or_announce_image: announces the in-Emacs build when no image is set" {
+@test "pull_or_announce_image: points at make telega-image when no image is set" {
TELEGA_DOCKER_IMAGE=""
run pull_or_announce_image
[ "$status" -eq 0 ]
- [[ "$output" == *"M-x telega-server-build"* ]]
+ [[ "$output" == *"make telega-image"* ]]
}
@test "pull_or_announce_image: pulls when TELEGA_DOCKER_IMAGE is set" {
diff --git a/tests/test-telega-config--docker-pin.el b/tests/test-telega-config--docker-pin.el
index 894dbfdd..8f5b62bd 100644
--- a/tests/test-telega-config--docker-pin.el
+++ b/tests/test-telega-config--docker-pin.el
@@ -5,9 +5,15 @@
;;
;; telega infers its image from `telega-tdlib-min-version' and only pins to a
;; version tag when min and max versions are equal and the version ends in
-;; ".0". This config has min "1.8.64" and max nil, so the inference always
+;; ".0". This config has min "1.8.66" and max nil, so the inference always
;; falls through to "zevlg/telega-server:latest" -- a moving tag that can
;; swap the server out from under a fixed elisp version without notice.
+;;
+;; Since 2026-08-25 the pin names an image built locally from
+;; docker/telega-server/Dockerfile (upstream's image is missing a shared
+;; library, zevlg/telega.el#596). Three files have to agree on that image:
+;; the defcustom default, the Makefile's build tag, and the Dockerfile's
+;; digest-pinned base. The tests below hold them together.
;;; Code:
@@ -17,6 +23,18 @@
(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(require 'telega-config)
+(defun test-telega-config--file-string (relative)
+ "Return the contents of RELATIVE under `user-emacs-directory'."
+ (with-temp-buffer
+ (insert-file-contents (expand-file-name relative user-emacs-directory))
+ (buffer-string)))
+
+(defun test-telega-config--pin-default ()
+ "Return the defcustom's shipped default, not the live value.
+Customizing the pin (including to nil) is not a test failure; only
+changing the shipped default is."
+ (eval (car (get 'cj/telega-docker-image 'standard-value)) t))
+
;; -- cj/--telega-docker-pinned-image -----------------------------------------
(ert-deftest test-telega-config-pin-returns-configured-reference ()
@@ -77,17 +95,118 @@ name, so this stays a reversible setting."
"Normal: the advice is a named function so it can be removed by reference."
(should (fboundp 'cj/--telega-docker-image-name)))
-(ert-deftest test-telega-config-pin-default-is-a-digest-reference ()
- "Normal: the shipped default pins by digest, not by a floating tag.
-A tag pin (:latest, or even a version tag upstream can re-push) still
-moves; a digest names one immutable image.
-
-Reads the defcustom's standard value rather than the live variable, so
-customizing the pin (including to nil, handing the choice back to telega)
-is not a test failure -- only changing the shipped default is."
- (let ((default (eval (car (get 'cj/telega-docker-image 'standard-value)) t)))
+;; -- the shipped default and the files it depends on -------------------------
+
+(ert-deftest test-telega-config-pin-default-matches-makefile-build-tag ()
+ "Normal: the default pin is exactly the tag `make telega-image' builds.
+The image is built locally, so the pin is a tag rather than a registry
+digest. The Makefile owns the tag; the defcustom must name the same one
+or a fresh machine builds an image telega never looks for."
+ (let ((makefile (test-telega-config--file-string "Makefile")))
+ (should (string-match "^TELEGA_IMAGE[ \t]*[?:]?=[ \t]*\\([^ \t\n]+\\)" makefile))
+ (should (equal (test-telega-config--pin-default)
+ (match-string 1 makefile)))))
+
+(ert-deftest test-telega-config-pin-default-is-a-local-tag-not-a-digest ()
+ "Boundary: the default is a plain tag, with no registry digest suffix.
+A locally built image has no RepoDigest, so a digest reference here could
+never resolve."
+ (let ((default (test-telega-config--pin-default)))
(should (stringp default))
- (should (string-match-p "@sha256:[0-9a-f]\\{64\\}\\'" default))))
+ (should (string-match-p "\\`[a-z0-9./-]+:[A-Za-z0-9._-]+\\'" default))
+ (should-not (string-match-p "@sha256:" default))))
+
+(ert-deftest test-telega-config-dockerfile-pins-base-image-by-digest ()
+ "Normal: the Dockerfile's base is an immutable upstream digest.
+This is where the digest guarantee the old pin gave now lives. A tag in
+the FROM line would let upstream swap the base under a rebuild."
+ (let ((dockerfile (test-telega-config--file-string "docker/telega-server/Dockerfile")))
+ (should (string-match-p
+ "^FROM zevlg/telega-server@sha256:[0-9a-f]\\{64\\}[ \t]*$"
+ dockerfile))))
+
+(ert-deftest test-telega-config-dockerfile-adds-the-missing-library ()
+ "Normal: the Dockerfile installs libglycin, the whole reason it exists.
+Upstream's image fails to start without it (zevlg/telega.el#596)."
+ (let ((dockerfile (test-telega-config--file-string "docker/telega-server/Dockerfile")))
+ (should (string-match-p "^RUN apk add .*libglycin" dockerfile))))
+
+;; -- cj/--telega-docker-image-present-p (the docker boundary) ----------------
+;; Exercised against a fake `docker' executable on a private exec-path rather
+;; than by mocking `call-process' (a subr; see the native-comp mocking gotcha).
+
+(defun test-telega-config--with-fake-docker (exit-code thunk)
+ "Call THUNK with a fake `docker' on `exec-path' that exits EXIT-CODE."
+ (let* ((dir (make-temp-file "fake-docker-" t))
+ (script (expand-file-name "docker" dir)))
+ (unwind-protect
+ (progn
+ (with-temp-file script
+ (insert (format "#!/bin/sh\nexit %d\n" exit-code)))
+ (set-file-modes script #o700)
+ (let ((exec-path (list dir)))
+ (funcall thunk)))
+ (delete-directory dir t))))
+
+(ert-deftest test-telega-config-image-present-p-true-when-inspect-succeeds ()
+ "Normal: `docker image inspect' exiting 0 means the image is present."
+ (test-telega-config--with-fake-docker 0
+ (lambda () (should (cj/--telega-docker-image-present-p "cj/telega-server:x")))))
+
+(ert-deftest test-telega-config-image-present-p-nil-when-inspect-fails ()
+ "Boundary: a non-zero exit (no such image) reads as not present."
+ (test-telega-config--with-fake-docker 1
+ (lambda () (should-not (cj/--telega-docker-image-present-p "cj/telega-server:x")))))
+
+(ert-deftest test-telega-config-image-present-p-nil-without-docker ()
+ "Error: with no docker on `exec-path', the helper returns nil instead of
+signalling, so the launcher can still route the user to the make target."
+ (let ((exec-path nil))
+ (should-not (cj/--telega-docker-image-present-p "cj/telega-server:x"))))
+
+;; -- cj/telega refuses to launch against a missing local image ---------------
+
+(ert-deftest test-telega-config-missing-image-message-names-image-and-target ()
+ "Normal: the message names the missing image and the make target that builds it."
+ (let ((msg (cj/--telega-missing-image-message "cj/telega-server:x")))
+ (should (string-match-p "cj/telega-server:x" msg))
+ (should (string-match-p "make telega-image" msg))))
+
+(ert-deftest test-telega-config-launcher-errors-when-pinned-image-is-absent ()
+ "Error: with a pin set and no such image, `cj/telega' stops with the make hint.
+Without this, docker fails to pull a local-only tag and the error names a
+registry the image was never meant to come from."
+ (let ((cj/telega-docker-image "cj/telega-server:x")
+ (launched nil))
+ (cl-letf (((symbol-function 'locate-library) (lambda (&rest _) "telega.el"))
+ ((symbol-function 'cj/--telega-docker-image-present-p) (lambda (_) nil))
+ ((symbol-function 'telega) (lambda (&rest _) (setq launched t))))
+ (let ((err (should-error (cj/telega) :type 'user-error)))
+ (should (string-match-p "make telega-image" (cadr err))))
+ (should-not launched))))
+
+(ert-deftest test-telega-config-launcher-runs-when-pinned-image-is-present ()
+ "Normal: with the pinned image present, `cj/telega' launches telega."
+ (let ((cj/telega-docker-image "cj/telega-server:x")
+ (launched nil))
+ (cl-letf (((symbol-function 'locate-library) (lambda (&rest _) "telega.el"))
+ ((symbol-function 'cj/--telega-docker-image-present-p) (lambda (_) t))
+ ((symbol-function 'telega) (lambda (&rest _) (setq launched t))))
+ (cj/telega)
+ (should launched))))
+
+(ert-deftest test-telega-config-launcher-skips-image-check-without-a-pin ()
+ "Boundary: with no pin, telega infers and pulls its own image; no check runs."
+ (let ((cj/telega-docker-image nil)
+ (checked nil)
+ (launched nil))
+ (cl-letf (((symbol-function 'locate-library) (lambda (&rest _) "telega.el"))
+ ((symbol-function 'cj/--telega-docker-image-present-p)
+ (lambda (_) (setq checked t) nil))
+ ((symbol-function 'telega) (lambda (&rest _) (setq launched t))))
+ (cj/telega)
+ (should-not checked)
+ (should launched))))
(provide 'test-telega-config--docker-pin)
;;; test-telega-config--docker-pin.el ends here
diff --git a/tests/test-telega-config.el b/tests/test-telega-config.el
index d8aaeb4d..54a601c9 100644
--- a/tests/test-telega-config.el
+++ b/tests/test-telega-config.el
@@ -45,6 +45,10 @@ stub's cryptic load-file failure."
(let (called)
(cl-letf (((symbol-function 'featurep)
(lambda (sym &optional _sub) (eq sym 'telega)))
+ ;; The pinned image is a local build; treat it as present so
+ ;; this test stays about delegation, not the image check.
+ ((symbol-function 'cj/--telega-docker-image-present-p)
+ (lambda (_) t))
((symbol-function 'telega)
(lambda (&rest _) (setq called t))))
(cj/telega))