Commitfef5afdcRecorded24 Aug 2026Repositorycore-channel

Correct five false comments and close tmux's one real host leak

Message

Findings from an adversarial review of the previous commit, run against its own checkout at a named SHA rather than this worktree. Every correction below was re-measured before being made.

Five comment statements were false:

  • "tmux is the channel's first package with a dependency SET" -- emacs has had five since it landed, (list ncurses zlib libxml2 sqlite tree-sitter). tmux is the first with a dependency set AND a build input at once.
  • --enable-static "pulls libevent's Libs.private onto the link line" -- all four libevent .pc files have an EMPTY Libs.private, and pkgconf --static --libs libevent_core returns what the non-static call does. -static in LDFLAGS is the whole of it.
  • the five --disable flags described as closing "use X if it is installed" probes -- read against tmux 3.7c's configure.ac all five are opt-in, off when unset, and error rather than absorbing the host when asked for and missing. They are documentation, not a hermeticity fix.
  • "each of libevent's four .pc files carries an empty Requires:" -- three of four; libevent_pthreads.pc has Requires: libevent.
  • the pkgconf empty-field abort attributed to a version difference -- the seed's own build script measured the identical defective line in 1.9.5 and 2.4.3 and states no release fixes it. A SOURCE PATCH the seed applies is what makes it safe. Naming 2.4.3 would invite the wrong repair later.

And one real defect, which is the leak the flags above were wrongly credited with closing. tmux's configure does:

  DEFAULT_LOCK_CMD="lock -np"
  AC_CHECK_PROG(found_vlock, vlock, yes, no)
  if found_vlock; then DEFAULT_LOCK_CMD="vlock"

The SEED ships a busybox vlock applet, so -DTMUX_LOCK_CMD='"vlock"' landed on every compile line -- a value chosen by what happened to be visible in the build environment, naming a program that is not in tmux's runtime closure. ac_cv_prog_found_vlock=no is autoconf's own override for a cached AC_CHECK_PROG; measured, it flips the result to lock-command... lock -np. Neither default is in the closure and lock-command is a runtime option; what changes is that the compiled default now comes from tmux's portable choice rather than from the seed's applet list.

Also documents two consequences of --enable-pc-files that a consumer will meet: the build environment's -s leaks into the published Libs:, and the generated files carry empty Requires.private:/Libs.private: fields, safe only because of the seed's patched pkgconf.

Changed
 src/core/packages/build-tools.sgl |   5 ++++-
 src/core/packages/event.sgl       |  24 ++++++++++++++++++------
 src/core/packages/terminal.sgl    | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
 3 files changed, 152 insertions(+), 39 deletions(-)
Diff
src/core/packages/build-tools.sglmodified
@@ -4,7 +4,10 @@
4
;;; exists to hold is not cosmetic. A `dependencies:` entry contributes
5
;;; `-I<dep>/include`, `-L<dep>/lib` and `<dep>/lib/pkgconfig`, and its store
6
;;; path can legitimately end up inside the built artifact. A `build-inputs:`
−7
;;; entry contributes its `bin/` to PATH (first, ahead of the seed) and nothing
+7
;;; entry contributes its `bin/` to PATH -- ahead of the seed, per
+8
;; builder-base-assignments in (sigil build external), though nothing in this
+9
;; change measures the ordering because neither tool exists in the seed at all
+10
;; -- and nothing
11
;;; else -- deliberately not `${name}` interpolation, because a baked tool path
12
;;; would be an undeclared RUNTIME reference to something that is only a
13
;;; build-time input.
src/core/packages/event.sglmodified
@@ -32,12 +32,24 @@
32
;;
33
;; The `.pc` files matter more than the headers here: tmux finds libevent
34
;; through PKG_CHECK_MODULES, and the builder puts every dependency's
−35
;; `lib/pkgconfig` on PKG_CONFIG_PATH already. Each of libevent's four
−36
;; `.pc` files carries an empty `Requires:` and an empty `Conflicts:`,
−37
;; which is exactly the shape that made the OLD seed's pkgconf 1.9.5 abort
−38
;; with SIGABRT. Measured against the seed this channel actually pins
−39
;; (pkgconf 2.4.3, seed-v2): accepted, exit 0, with an absent-package
−40
;; control still exiting 1.
+35
;; `lib/pkgconfig` on PKG_CONFIG_PATH already. Measured on the installed
+36
;; item, THREE of the four generated files carry an empty `Requires:`
+37
;; (`libevent_pthreads.pc` has `Requires: libevent`) and all four carry an
+38
;; empty `Conflicts:`. That empty-dependency-list shape is what makes
+39
;; pkgconf read past the start of its buffer and abort with SIGABRT under
+40
;; Zig's runtime safety.
+41
;;
+42
;; IT IS NOT A VERSION PROBLEM AND MUST NOT BE RECORDED AS ONE. The seed's
+43
;; own build script measured the identical defective line in pkgconf 1.9.5
+44
;; (parser.c:88) and 2.4.3 (parser.c:95) and states plainly that no pkgconf
+45
;; release fixes it -- a version bump is not a fix. What makes the seed's
+46
;; pkgconf safe is a SOURCE PATCH the seed applies itself, plus two
+47
;; refusals guarding it; see seed/scripts/build-seed.sh. Naming "2.4.3" as
+48
;; the reason would invite exactly the wrong repair later.
+49
;;
+50
;; Verified against the seed's own binary on all four files this package
+51
;; installs: `--exists`, `--cflags --libs` and `--static` all exit 0, with
+52
;; an absent-package negative control still exiting 1.
53
(define libevent
54
(package
55
name: "libevent"
src/core/packages/terminal.sglmodified
@@ -5,12 +5,14 @@
5
;;; ncurses' own install rule otherwise writes the compiled terminfo database
6
;;; outside DESTDIR; see the note beside the `install` phase below.
7
;;;
−8
;;; tmux is the channel's first package with a dependency SET rather than a
−9
;;; single edge, and it carries a build input as well. Those are two different
−10
;;; relationships and tmux has one of each: ncurses and libevent are linked
−11
;;; into the output, byacc only has to exist on PATH while configure runs.
−12
;;; ncurses gained a build input of its own for the same reason -- see the
−13
;;; note beside its `build-inputs:` below.
+8
;;; tmux is the first package here that has BOTH kinds of input at once: a
+9
;;; dependency SET (ncurses and libevent, linked into the output) and a BUILD
+10
;;; INPUT (byacc, which only has to exist on PATH while configure runs). It is
+11
;;; NOT the channel's first multi-dependency package -- emacs has had five
+12
;;; since it landed, `(list ncurses zlib libxml2 sqlite tree-sitter)` in
+13
;;; (core packages editors). ncurses gained a build input of its own in the
+14
;;; same change, for an unrelated reason; see the note beside its
+15
;;; `build-inputs:` below.
16
(define-library (core packages terminal)
17
(import (sigil core)
18
(core build-environments)
@@ -109,6 +111,36 @@
111
;; `forkpty... none required`, and a final
112
;; LIBS carrying `-lncursesw` and no
113
;; `-lncurses`.
+114
;;
+115
;; TWO CONSEQUENCES OF GENERATING THESE FILES,
+116
;; both measured on the installed item, because
+117
;; a `.pc` is a consumer-facing interface and
+118
;; these are what consumers will see:
+119
;;
+120
;; * `Libs:` comes out as
+121
;; `-L<out>/lib -s -lncursesw`. The `-s`
+122
;; is the build environment's own LDFLAGS
+123
;; leaking into a published interface, so
+124
;; every pkg-config consumer is silently
+125
;; force-stripped -- and this package
+126
;; passes `--disable-stripping` two lines
+127
;; down. Not fixed here; ncurses'
+128
;; gen-pkgconfig substitutes $LDFLAGS and
+129
;; removing it needs a patch.
+130
;;
+131
;; * `Requires.private:` and `Libs.private:`
+132
;; are emitted EMPTY -- the exact shape
+133
;; src/patches/libxml2-pc-drop-empty-
+134
;; requires.patch exists to remove. It is
+135
;; safe only because the seed's pkgconf
+136
;; carries a SOURCE PATCH for the
+137
;; underflow; see seed/scripts/
+138
;; build-seed.sh, which measured the same
+139
;; defective line in 1.9.5 and 2.4.3 and
+140
;; states that no pkgconf release fixes it.
+141
;; Verified against the seed's own binary
+142
;; on all four generated files: rc 0, with
+143
;; an absent-package control still rc 1.
144
"--enable-pc-files"
145
"--with-pkg-config-libdir=${out}/lib/pkgconfig"))
146
(external-phase kind: 'make)
@@ -125,18 +157,36 @@
157
158
;; TERMINFO IS THE INTERESTING PART OF THIS PACKAGE, not the link.
159
;;
−128
;; tmux is a terminal multiplexer: it allocates a pty, and it reads a
−129
;; terminfo entry to know what to write into it. A tmux that starts only
−130
;; because the HOST happens to have `/usr/share/terminfo` is the exact
−131
;; false green this channel exists to prevent, and `tmux -V` cannot tell
−132
;; the two apart -- it proves the binary links and nothing else.
+160
;; tmux is a terminal multiplexer: it allocates a pty, and a tmux CLIENT
+161
;; reads a terminfo entry to know what to write into it. `tmux -V` cannot
+162
;; see any of that -- it proves the binary links and nothing else. Nor can
+163
;; a detached `new-session -d`: no client attaches, so no terminfo entry is
+164
;; ever loaded and every arm of such a test passes, including the ones
+165
;; meant to fail.
+166
;;
+167
;; WHERE THE TERMINFO ACTUALLY COMES FROM, stated exactly, because the
+168
;; comfortable version of this is wrong. ncurses is linked statically, so
+169
;; its `--with-default-terminfo-dir=${out}/share/terminfo` IS carried into
+170
;; the tmux binary -- `grep -a` finds `/sigil/store/<ncurses-item>/share/
+171
;; terminfo` in it, and that is the only store path the binary contains.
+172
;; But `/sigil/store` is the SANDBOX's view of the store and exists only
+173
;; during a build, so OUTSIDE the sandbox that compiled-in default resolves
+174
;; to nothing:
+175
;;
+176
;; TERMINFO_DIRS = the ncurses store item -> session runs, output captured
+177
;; TERMINFO_DIRS = an empty directory -> "missing or unsuitable terminal"
+178
;; TERMINFO_DIRS = a nonexistent path -> "can't find terminfo database"
+179
;; TERMINFO_DIRS unset (compiled-in only) -> "can't find terminfo database"
+180
;; TERMINFO_DIRS = the host's guix profile-> session runs, output captured
181
;;
−134
;; What makes the store copy authoritative is ncurses' own
−135
;; `--with-default-terminfo-dir=${out}/share/terminfo`, above. That path
−136
;; is compiled into `libncursesw.a`, so linking it statically carries
−137
;; ncurses' store item into this binary as its terminfo root. ncurses is
−138
;; a declared dependency, so that is a true reference rather than a
−139
;; smuggled one.
+182
;; So the store's terminfo works and is load-bearing, and tmux CANNOT reach
+183
;; it on its own: something must hand it the path. Today `sigil env shell`
+184
;; hands it the HOST's, because it exports a hardcoded search-path list
+185
;; applied to the lock's ROOT packages only and never consults a package's
+186
;; own `search-paths:` -- so ncurses' terminfo, a DEPENDENCY's, is not on
+187
;; the list at all. That is a `sigil env` defect, not a packaging one, and
+188
;; it is tracked outside this repository. Recorded here so the next reader
+189
;; does not conclude from a working tmux that the store is what fed it.
190
(define tmux
191
(package
192
name: "tmux"
@@ -145,12 +195,12 @@
195
location: "https://github.com/tmux/tmux/releases/download/3.7c/tmux-3.7c.tar.gz"
196
sha256: "7c60cae9a0e25288e2e24750aafc9e8800fc7fd4555e447e1b29ee4201cfb3bf")
197
buildenv: core-buildenv
−148
;; The channel's first dependency SET. Nothing here says how either
−149
;; dependency is laid out: ncurses exports `CPATH=include/ncursesw`
−150
;; itself, libevent needs no export because its headers sit directly
−151
;; in `include/`, and the builder contributes `-I`, `-L` and
−152
;; `PKG_CONFIG_PATH` for both. Order is not significant -- the engine
−153
;; sorts dependencies by name before sealing them.
+198
;; Nothing here says how either dependency is laid out: ncurses exports
+199
;; `CPATH=include/ncursesw` itself, libevent needs no export because
+200
;; its headers sit directly in `include/`, and the builder contributes
+201
;; `-I`, `-L` and `PKG_CONFIG_PATH` for both. Order is not significant
+202
;; -- the engine sorts dependencies by name before sealing them, which
+203
;; is visible in any objects/<id>.sgl.
204
dependencies: (list ncurses libevent)
205
;; byacc is here and NOT in `dependencies:` because nothing it produces
206
;; is linked into tmux. configure demands a program called `yacc` on
@@ -163,23 +213,71 @@
213
kind: 'configure
214
args: (list "--prefix=${out}"
215
;; every other package in the channel is
−166
;; static, and this also makes configure ask
−167
;; pkg-config for `--static`, which is what
−168
;; pulls libevent's `Libs.private` onto the
−169
;; link line
+216
;; static. It puts `-static` on LDFLAGS and
+217
;; makes configure call pkg-config with
+218
;; `--static`. The pkg-config half buys
+219
;; nothing TODAY -- all four of libevent's
+220
;; `.pc` files have an empty `Libs.private:`,
+221
;; and `pkgconf --static --libs libevent_core`
+222
;; returns exactly what the non-static call
+223
;; does -- so `-static` is the whole of it.
224
"--enable-static"
225
;; the default already, stated so the build
226
;; does not depend on what configure happens
227
;; to find probeable
228
"--with-TERM=screen"
−175
;; none of these are in the channel, and each
−176
;; is a `use X if it is installed` probe that
−177
;; would otherwise make the output depend on
−178
;; the host
+229
;; None of these are in the channel. Stated
+230
;; explicitly rather than left to the default,
+231
;; but be clear about what that buys: read
+232
;; against tmux 3.7c's configure.ac, ALL FIVE
+233
;; are opt-in and already off when unset
+234
;; (utf8proc and jemalloc probe automatically
+235
;; only on darwin), and each ERRORS rather
+236
;; than silently absorbing the host when asked
+237
;; for and missing. So these flags are
+238
;; documentation, not a hermeticity fix. The
+239
;; probe that genuinely DID leak the build
+240
;; environment into the output is the lock
+241
;; command, below.
242
"--disable-utf8proc"
243
"--disable-utempter"
244
"--disable-jemalloc"
245
"--disable-systemd"
−183
"--disable-fuzzing"))
+246
"--disable-fuzzing"
+247
;; A REAL HOST LEAK, and it is the only one
+248
;; tmux's configure leaves open here.
+249
;;
+250
;; DEFAULT_LOCK_CMD="lock -np"
+251
;; AC_CHECK_PROG(found_vlock, vlock, yes, no)
+252
;; if found_vlock; then DEFAULT_LOCK_CMD="vlock"
+253
;;
+254
;; Measured: without this override configure
+255
;; reports `lock-command... vlock`, because the
+256
;; SEED ships a busybox `vlock` applet, and
+257
;; `-DTMUX_LOCK_CMD='"vlock"'` lands on every
+258
;; compile line. That is a value chosen by
+259
;; what happened to be visible in the build
+260
;; environment rather than by anything this
+261
;; package declares -- and `vlock` is not in
+262
;; tmux's runtime closure, so `lock-server`
+263
;; would shell out to a program that is not
+264
;; there.
+265
;;
+266
;; `ac_cv_prog_found_vlock=no` is autoconf's
+267
;; own documented override (AC_CHECK_PROG IS
+268
;; cached; `ac_cv_prog_found_vlock` appears in
+269
;; the generated configure, against a control
+270
;; showing 89 `ac_cv_prog_` variables there).
+271
;; Measured: it flips the result to
+272
;; `lock-command... lock -np`.
+273
;;
+274
;; HONEST ABOUT WHAT THIS FIXES: `lock -np` is
+275
;; not in the closure either. Neither default
+276
;; is runnable, and `lock-command` is a runtime
+277
;; server option anyone can set. What changes
+278
;; is that the compiled default now comes from
+279
;; tmux's own portable choice instead of from
+280
;; the seed's applet list.
+281
"ac_cv_prog_found_vlock=no"))
282
(external-phase kind: 'make)
283
(external-phase kind: 'install))))))