Render: a tracked symlink to a directory is named, not read
git ls-tree lists a tracked symlink as one leaf (mode 120000). When it points at a directory, file-verdict read it as a file, raised `read-string: Is a directory', and aborted the WHOLE project render. Found 2026-09-23 rendering the real estate for the code.usesigil.org move: four Sigil repos track such links (sigil's test corpus links; .sigil/deps/* in sigil-rss, sigil-twitch, sigil-web-styles), and each one alone killed the Sigil project's render.
The link now gets no page and is NAMED with the other exclusions ("a symbolic link to a directory; git tracks the link, not its contents"). `directory?' follows links (probed: link-to-dir #t, real dir #t, link-to-file #f), so a link to a FILE still renders its target.
test-render.sh builds a repo tracking both kinds, with the file target outside the repo so only the link can put its text on the site. It asserts the render succeeds, the directory link is named as excluded, and the file link's content is rendered. green: rc=0, render gate GREEN red (clause disabled, hash-guarded plant, rebuilt): rc=1, "a tracked symlink to a directory aborted the render", the original read-string error; restored to the pre-plant hash and rebuilt.
src/docket/render.sgl | 10 ++++++++++
test/integration/test-render.sh | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+)src/docket/render.sglmodified
(cons "the path contains # or ?, which cannot appear unescaped in a URL" #f)) ((not (file-exists? (path-join dir rel))) (cons "no such file on disk at tip" #f)) ;; A TRACKED SYMLINK TO A DIRECTORY. git ls-tree lists the link as ;; one leaf (mode 120000); on disk it resolves to a directory, and ;; reading it as a file raised `read-string: Is a directory', which ;; aborted the WHOLE render. Found 2026-09-23 rendering the real ;; estate: sigil (test corpus links), sigil-rss, sigil-twitch and ;; sigil-web-styles (tracked .sigil/deps links) each killed the ;; Sigil project's render on its own. Named, not rendered: git ;; tracks the link, not what it points at. ((directory? (path-join dir rel)) (cons "a symbolic link to a directory; git tracks the link, not its contents" #f)) (else (let ((text (read-file-string (path-join dir rel)))) (if (binary-string? text) (cons "binary" #f) (cons #f text))))))test/integration/test-render.shmodified
--project "$repo/test/fixtures/chronicle/project.sgl" --repos "$tmp/repos-dormant.json" \ --output "$tmp/site-dormant" --report-url "http://127.0.0.1:8765/" \ --site-url 'https://example.test' >/dev/null# A TRACKED SYMLINK TO A DIRECTORY MUST NOT KILL THE RENDER. Found 2026-09-23# on the real estate: four Sigil repos track such links (.sigil/deps/*, test# corpus links), and each one alone aborted the project's render with# `read-string: Is a directory'. The link gets no page and is NAMED, like every# other exclusion. The link to a FILE is the positive control: it still renders.mkdir -p "$tmp/repos/linky/realdir"cp "$repo/test/fixtures/chronicle/sigil-web/package.sgl" "$tmp/repos/linky/package.sgl"printf 'inside the linked directory\n' >"$tmp/repos/linky/realdir/inner.txt"# The file target lives OUTSIDE the repo, so its text can reach the site only# through the link -- otherwise target.txt's own page would satisfy the# positive control below whether or not the link rendered.printf 'the linked file\n' >"$tmp/linky-target.txt"ln -s realdir "$tmp/repos/linky/linkdir"ln -s "$tmp/linky-target.txt" "$tmp/repos/linky/linkfile.txt"git -C "$tmp/repos/linky" init -qgit -C "$tmp/repos/linky" -c user.name='Docket TEST-ONLY' -c user.email=test-only@example.invalid add .git -C "$tmp/repos/linky" -c user.name='Docket TEST-ONLY' -c user.email=test-only@example.invalid \ commit -q -m 'TEST-ONLY tracked symlinks'[[ $(git -C "$tmp/repos/linky" ls-files -s linkdir | cut -d' ' -f1) == 120000 ]] || { echo 'FAIL: fixture setup -- linkdir is not tracked as a symlink, so this test proves nothing' >&2; exit 1; }printf '[{"name":"linky","path":"%s","package":"%s"}]\n' \ "$tmp/repos/linky" "$tmp/repos/linky/package.sgl" >"$tmp/repos-linky.json"rm -rf "$tmp/site-linky"linky_out=$("$repo/docket" render --record "$tmp/emptyrec" --state "$tmp/emptystate" \ --project "$repo/test/fixtures/chronicle/project.sgl" --repos "$tmp/repos-linky.json" \ --output "$tmp/site-linky" --report-url "http://127.0.0.1:8765/" 2>&1) || { printf '%s\n' "$linky_out" >&2 echo 'FAIL: a tracked symlink to a directory aborted the render' >&2; exit 1; }grep -Fq 'excluded linkdir (a symbolic link to a directory' <<<"$linky_out" || { printf '%s\n' "$linky_out" >&2 echo 'FAIL: the symlinked directory was not NAMED as excluded' >&2; exit 1; }grep -rlFq 'the linked file' "$tmp/site-linky/sigil/repos/linky/" || { echo 'FAIL: positive control -- the symlink to a FILE no longer renders its content' >&2; exit 1; }# --site-url IS REQUIRED HERE, and not for realism. Without it NO feed is ever# written, so every feed assertion below -- that one exists, that it is dated# from the record, that the page links it -- would be asking about a render