Skip to content

Instantly share code, notes, and snippets.

@lewang
Created July 29, 2026 13:52
Show Gist options
  • Select an option

  • Save lewang/8b7ae8aafc54b7074df05510bb732ed4 to your computer and use it in GitHub Desktop.

Select an option

Save lewang/8b7ae8aafc54b7074df05510bb732ed4 to your computer and use it in GitHub Desktop.
Reproduce denote PR #715: denote: link previews abort org-mode startup inside a silo
#!/bin/sh
# Reproduce the per-commit table in comment.md by running repro-715.sh against
# four builds of denote: upstream, each commit of PR #715 alone, and both.
#
# sh matrix.sh /path/to/denote-repo
#
# Each build is a throwaway git worktree at the pre-PR upstream tip with the
# relevant denote.el hunks applied. Only denote.el is applied, not the test
# files, so the two commits can be taken in isolation without conflicting.
# Every worktree is removed on exit, so nothing is left registered in the repo.
set -eu
REPO=$(cd "${1:?usage: matrix.sh /path/to/denote-repo}" && pwd)
HERE=$(cd "$(dirname "$0")" && pwd)
BASE=1004f73 # upstream tip immediately before the PR
DIRLOCAL=2fe3bcd # Honor unapplied dir-local denote-directory
NILGUARD=d9d1257 # Do not attempt a preview for unresolvable denote links
W=$(mktemp -d "${TMPDIR:-/tmp}/denote-715-matrix.XXXXXX")
# One init directory for all four runs, so Org is byte-compiled once.
DENOTE_715_EMACSD="$W/emacs.d"
export DENOTE_715_EMACSD
cleanup() {
for d in "$W"/*; do
[ -d "$d" ] && git -C "$REPO" worktree remove --force "$d" 2>/dev/null || true
done
rm -rf "$W"
git -C "$REPO" worktree prune
}
trap cleanup EXIT
build() {
name=$1; shift
git -C "$REPO" worktree add --detach "$W/$name" "$BASE" >/dev/null 2>&1
for sha in "$@"; do
git -C "$REPO" show "$sha" -- denote.el | git -C "$W/$name" apply -
done
}
build base
build nilguard "$NILGUARD"
build dirlocal "$DIRLOCAL"
build both "$NILGUARD" "$DIRLOCAL"
for name in base nilguard dirlocal both; do
echo "################ $name ################"
sh "$HERE/repro-715.sh" "$W/$name" | sed -n '3,$p'
echo
done
#!/bin/sh
# Reproduce the failure fixed by denote PR #715.
#
# sh repro-715.sh /path/to/denote-checkout
#
# Everything lives in a throwaway directory: a pristine init directory whose
# only package is Org, and a two-file silo. Your own configuration, your own
# package directory and your own notes are never read or written.
#
# Two preconditions are load-bearing, and either one alone hides the bug:
#
# 1. Org >= 9.8. The `:preview' link parameter that denote registers
# `denote-link-preview-file' on first shipped in Org 9.8 (released
# 2026-02-21). On the Org bundled with every released Emacs -- 9.7.x --
# denote's preview function is never called and there is nothing to see.
# Stock package.el pulls a current Org from GNU ELPA below.
#
# 2. A graphical frame. `org-link-preview-file' opens with
# (when (display-graphic-p) ...), so under --batch the preview is skipped
# and the run looks perfectly clean. The probe therefore opens a real
# frame and exits on its own.
set -eu
DENOTE=${1:?usage: repro-715.sh /path/to/denote-checkout}
DENOTE=$(cd "$DENOTE" && pwd)
T=$(mktemp -d "${TMPDIR:-/tmp}/denote-715.XXXXXX")
trap 'rm -rf "$T"' EXIT
# --------------------------------------------------------------------------
# A pristine init directory. Stock package.el, GNU ELPA, nothing else.
#
# Set DENOTE_715_EMACSD to reuse one init directory across several runs; Org is
# then installed and byte-compiled only once. Left unset, the init directory
# lives and dies with this run.
# --------------------------------------------------------------------------
ED=${DENOTE_715_EMACSD:-"$T/emacs.d"}
mkdir -p "$ED"
[ -f "$ED/init.el" ] || cat > "$ED/init.el" <<'EOF'
;;; init.el --- get a current Org from GNU ELPA -*- lexical-binding: t -*-
(require 'package)
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/"))
package-install-upgrade-built-in t)
(package-initialize)
(unless (assq 'org package-alist)
(package-refresh-contents)
(package-install 'org))
EOF
# --------------------------------------------------------------------------
# The silo: a dir-local `denote-directory', one note, one image to preview.
# --------------------------------------------------------------------------
mkdir -p "$T/silo" "$T/elsewhere"
cat > "$T/silo/.dir-locals.el" <<EOF
((nil . ((denote-directory . "$T/silo"))))
EOF
cat > "$T/silo/20260101T000000--note__test.org" <<'EOF'
#+title: note
#+date: [2026-01-01 Thu 00:00]
#+filetags: :test:
#+identifier: 20260101T000000
[[denote:20260101T000001]]
EOF
# 1x1 transparent PNG, denote-named so the identifier resolves in the silo.
printf '%s' 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' \
| base64 -d > "$T/silo/20260101T000001--pic__test.png"
# --------------------------------------------------------------------------
# The probe. Opens the note the way any user would and reports what happened.
# --------------------------------------------------------------------------
cat > "$T/probe.el" <<'EOF'
;;; probe.el --- open a silo note with startup link previews -*- lexical-binding: t -*-
(let* ((dir (getenv "REPRO_DIR"))
(note (expand-file-name "silo/20260101T000000--note__test.org" dir))
msgs)
;; A global `denote-directory' that is NOT the silo -- the ordinary setup of
;; someone who keeps a main notes directory plus silos. Pointed at an empty
;; scratch directory so the run cannot create anything under $HOME.
(setq-default denote-directory (expand-file-name "elsewhere" dir))
(require 'denote)
(setq org-startup-with-link-previews t)
(ignore-errors (find-file note))
(setq msgs (with-current-buffer "*Messages*" (buffer-string)))
;; Read every field in the note's own buffer, then write. `with-temp-file'
;; would otherwise report on its own temp buffer.
(let (text)
(with-current-buffer (get-file-buffer note)
(setq text
(list
(format "Org version %s\n" (org-version))
(format "graphical frame %s\n" (if (display-graphic-p) "yes" "NO -- bug cannot appear"))
(format "major mode %s\n" major-mode)
(format "dir-locals applied %s\n"
(if (local-variable-p 'denote-directory) "yes" "NO <-- hack-local-variables never ran"))
(format "denote-directory %s\n" denote-directory)
(format "link previews rendered %d\n"
(length (seq-filter (lambda (o) (overlay-get o 'org-image-overlay))
(overlays-in (point-min) (point-max)))))
(format "error in *Messages* %s\n"
(if (string-match "File mode specification error.*" msgs)
(match-string 0 msgs)
"none")))))
(with-temp-file (expand-file-name "out.txt" dir)
(apply #'insert text))))
(kill-emacs 0)
EOF
# --------------------------------------------------------------------------
# Run. First hop is batch and only installs Org; the second opens a frame.
# --------------------------------------------------------------------------
emacs -Q --batch --init-directory="$ED" -l "$ED/init.el" \
--eval '(require (quote org))' >/dev/null 2>&1
REPRO_DIR="$T" emacs -Q --init-directory="$ED" -l "$ED/init.el" \
-L "$DENOTE" -l "$T/probe.el" >/dev/null 2>&1
echo "denote $DENOTE"
echo "denote revision $(git -C "$DENOTE" rev-parse --short HEAD 2>/dev/null || echo 'not a git checkout')"
cat "$T/out.txt"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment