You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracker for the dead-code audit (2026-07-16). Scope decided up front: our code only (core/, scripts/, hub/hub.c, build glue) — vendored deps (lua/, luasocket/, luasec/, …) are report-only. Conservative on contract surface: anything in docs/PLUGIN_API.md or core/scripts.lua's SANDBOX_GLOBALS stays even if unused internally, because third-party hubowner plugins we never see can reach it.
Method: 10 parallel agents on disjoint axes, then main-thread synthesis. Every finding below is file:line-anchored; each still needs re-derivation from source before removal (§1a.4) — the audit already produced three wrong calls, see "Lessons" at the bottom.
Already fixed (the audit's real yield so far)
These were not dead code — they fell out while looking:
fix(scripts): resolve plugin lang lookups; guard the class repo-wide #442 — etc_cmdlog read lang.failmsg1/2, keys are msg_denied/msg_nofile → German hubs saw English. Second instance of the class, so the guard is now repo-wide (tests/unit/plugin_lang_test.lua, 68 plugins). Found a third (cmd_delreg). Merged.
PR 1 — core/hub.lua dead file-scope locals (highest value)
Why first:hub.lua's main chunk sits at Lua's 200-local cap, and CLAUDE.md §3 therefore freezes file-scope locals ("any new top-level local fails the build"). These 19 are declared and never used — removing them frees 19 slots and un-freezes the file. Smallest diff, biggest structural win.
Declared-and-never-referenced (each verified whole-file, matches are the declaration plus comment prose only):
Line
Local
Note
167
tablesize
last consumer was the PING UC field; #179 replaced it
177
loadfile
183
io
194
table_concat
hub_user_object has its own
211
adclib_hash
moved to hub_dispatch (Phase 6d)
214
adclib_hashpas
"
217
adclib_createsalt
"
218
adclib_hasholdpas
"
269
types_check
types.add / types_utf8 are separate and live
272
util_difftime
moved to hub_dispatch
305
killuser
never assigned; body is the commented block at 1120-1158
310
usercount
never assigned — see below
322
isuseronline
assigned at 1025, never called
336-339
_verify, _normal, _protocol, _identify
never assigned; the four state tables live in hub_dispatch since Phase 6d
342
_G
trap, see below
394
_cfg_nick_change
never assigned/read. The nick_change FEATURE is live (cfg key read by cmd_nickchange / hub_inf_manager) — only hub.lua's stale cache slot is dead
_G removal trap (would be a #353-class boot crash):local _G (342) + _G = _G (525) must go together. Delete only the local and line 525 becomes a bare global write → the restricted env raises "attempt to write undeclared var" at hub load. Delete both or neither.
Riding along:
hub.lua:1025-1043isuseronline() — 19 lines, defined, never called, deliberately commented out of the export at 1276. Strict superset of the live isnickonline/iscidonline/issidonline trio.
hub.lua:1120-1158killuser — 39 commented-out lines whose only purpose is to justify a dead local slot.
hub.lua:310 + :1263 — hub.usercount is a nil export. Never assigned anywhere in the repo's history (git log -S"usercount = function" is empty), so { usercount = nil } never creates the key and pairs(hub) never sees it. Any plugin calling it gets "attempt to call a nil value". Not contract surface — a 15-year-old broken stub. 2 lines.
PR 2 — core/doc.lua, whole module (309 lines)
Commented out of _core (init.lua:121--"doc",), its doc.export() call site commented out (init.lua:328), its own 19 doc.add(...) payload lines sit inside a --[[ ]] block so even if loaded it would document nothing, its output target docs/documentation.txt does not exist, zero use "doc" anywhere, not in SANDBOX_GLOBALS, not in PLUGIN_API.md. Untouched since the SourceForge migration. install(DIRECTORY core/) is wholesale so no build change needed.
Nuance: it is a deliberate disable (two separate comment-outs), so it is a maintainer call whether to keep it as a dormant design artifact. PLUGIN_API.md supersedes it.
Also: init.lua:202--"test", references core/test.lua, which does not exist. 1 line.
:1019-1056addclient + :247-290wrapclient + :80socket_tcp — no in-tree caller (the source says so itself at :1025-1028); wrapclient is called only by addclient. server is not in SANDBOX_GLOBALS so it is not plugin-reachable. ~85 lines. Judgement call: this is the only outbound-TCP convenience wrapper; http_client does outbound via socket + server.addtimer instead. Two latent bugs die with it (ignored wrapclient return → handler = nil on the timeout path; unguarded failure(...) when listeners.failure is nil).
:914-955 the handler.starttls / deferred-TLS branch — starttls is never called. Inside it: needtls (516/918/950) written never read; handler.receivedata = handler.handshake / dispatchdata = handler.handshake (952-953) assign nil because handler.handshake does not exist; local shutdown (480) assigned 936/967, never called. ~45 lines. Judgement call: removes the STARTTLS seam. The no-op sub-defects inside are worth fixing regardless of what happens to the block.
:1185-1203getsettings / changesettings — exported, zero callers anywhere. ~20 lines.
:1214-1216stats — exported, zero callers; cascade: _readtraffic / _sendtraffic (163-164, incremented 708/788) are read only there. ~10 lines. Note handler.getstats (the per-connection twin, :544) is LIVE (cmd_userinfo.lua:212) — do not confuse them.
:633-636handler.pattern + the dead pattern parameter threaded through wrapclient / wrapserver / wrapconnection / addclient and p.pattern at :1138 — no caller ever supplies it; vestige of Phase 8 replacing LuaSocket's "*l" with the iostream pipeline.
Unused accessors, zero call sites: handler.bufferlen (637-641), handler.id (531-533 + 369-371), handler.dispatch (534-536), handler.disconnect (537-539), handler.socket (401-403). ~20 lines. Live siblings that must not be touched:handler.ssl, handler.getsslinfo, handler.setlistener, the server-handler shutdown, handler.getstats.
:249 / :300 — the disconnect binding in wrapclient / wrapserver is unused (wrapconnection's at :487is live — do not touch).
PR 4 — dead unreachable branches (no-ops, but they are landmines)
All confirmed by executing the shapes under the hub's Lua 5.4.8, not by reading:
core/util.lua:927-930 + 948-951 — encode/decode's else branches are unreachable (local str = tostring( str ) above makes if str then always true, tostring(nil) is the truthy string "nil"). Worse: the dead branch reads tbl, undeclared in that scope — under the restricted env it would raise "attempt to read undeclared var" instead of returning (nil, err). Disarmed only by being unreachable. util.encode/decode themselves are contract (PLUGIN_API §5.3) — keep the functions, drop the branches.
core/util.lua:638-641, 671-674, 743-746, 747-750 — four dead tonumber-shadow type guards (formatseconds, formatbytes, difftime ×2). Same shape Fix type check for number in util.lua #439 "fixed": local t = tonumber( t ) + if not t then return guarantees a number, so the type check can never fire either way. Pure no-op deletions. Do not touchconvertepochdate (791-793) / generatepass (702-703) — same shadow but correctly written and reachable.
PR 5 — small dead locals across the rest (~60 lines, trivially reviewable)
core/cfg.lua Phase-6c extraction leftovers (470, 488-489, 491-496, 528+540 — 11 lines; note removing the types_* set also orphans local types = use "types", which currently triggers types.lua's load — safe because cfg_defaults.lua:42 does the same, but say so in the PR body) · core/scripts.lua (31, 43, 56, 59, 87, 330 — 7 lines) · core/util.lua (90, 104, 130, 136, 140+204, 190 — 7 lines) · core/hub_dispatch.lua:68 · core/hub_user_object.lua:52-53, 293, 632 · core/hub_bot_object.lua:34, 94, 311 · core/hbri.lua:70+72 (+ the now-false hbri_test.lua:35-38 stub/comment) · core/adc.lua:45, 47, 59, 62, 82 + :1127-1128 (parse-local shadows of the module-level _contextsend/_contextdirect) · core/ratelimit.lua:35 · core/ipmatch.lua:69, 70, 72 · core/blocklist.lua:100 + :191 + agg.last_seen (235/254, written never read — LRU vestiges) · core/whitelist.lua:240-247_rebuild_indices (copy-pasted from blocklist, whose only caller is a bulk_replace whitelist deliberately does not have) · core/http.lua:57/125-131/215logsafe (+ its orphaned string_sub/string_gsub) · core/http_events.lua:68/363-364_max_size, :322types_str · core/http_router.lua:66, 73 · core/http_filter.lua:52 · core/cfg_secret.lua:66, 69, 70, 80 + is_active (264-266/320, whose only consumer is itself a dead local at cfg_users.lua:59) · core/cfg_users.lua:62, 69+74 · core/secrets.lua:52 · core/sysinfo.lua:34 · core/out.lua:16, 35 · core/types.lua:14, 15, 39, 40 · core/util.lua:535-539 (savetbl builds and sorts a tmp that is never read, then calls iterate(tbl) which rebuilds it — runs on everyuser.tbl save)
PR 6 — plugins (~57 lines)
Dead exports not in the PLUGIN_API §8 contract table: etc_report.lua:73-82+:91broadcast() (whole function; 96 callers use report.send, zero use .broadcast) · etc_usercommands.lua:89format · bot_opchat.lua:399bot · cmd_ban.lua:1259bans_path. Plus 17 dead local X = lang.Y or "..." locals and their now-orphaned lang keys (40 lines), and 2 never-looked-up lang keys (cmd_reg ucmd_passwort, etc_userlogininfo msg_ccpm_1/2/3).
Reported, deliberately NOT removed
CONTRACT / sandbox-reachable:usr_uptime.tbl, etc_trafficmanager.add, mmdb.MAX_FILE_SIZE, hmac.BLOCK_SIZE/sha256_bytes, whitelist.reload, util.handlebom, secrets.list_secret_keys, ipmatch.family/normalize, user.getblom, hub_bot_object.lua:252-256bot.reply (the stub must stay — removing it turns a silent no-op into a nil-call crash for unseen plugins; only its 5 commented body lines are dead).
Vendored (merge-cost on dep bumps):luasocket/src/usocket.c:106-118socket_select() — newly orphaned on POSIX by our own Hub event loop hits FD_SETSIZE=1024 Linux limit (select -> poll port) #310 patch; :42-73 the SOCKET_SELECT variant of socket_waitfd (macro never defined anywhere); luasec/src/luasocket/ — 5 uncompiled .c files, but its headers are LIVE (luasec/CMakeLists.txt:29-32 puts them on the include path and ssl.c includes them), a latent ODR trap worth its own issue.
Lua 5.1→5.4 migration leftovers: zero (Phase 3 left no compat scaffolding). Unused plugin helper functions: zero of 336. en/de lang-file key drift: zero. Plugins missing from cfg.tbl: zero. Dead cfg toggles: zero. Read-but-undefined cfg keys in-repo: zero. hub/hub.c: essentially clean (all 7 lua_register globals live, all 12 statics called). Dead cfg keys: no_cid_taken, ranks, owner_rank, use_ping, op_rank/admin_rank (cfg_defaults.lua 731/736/778/4872/768/773) — reachable via GET /v1/config, so PUT /v1/config/use_ping returns 200 and does nothing. Always-true validator: ssl_params (cfg_defaults.lua:4439) is the only one of 448 that never rejects — arguably deliberate (nested TLS table), report only.
Lessons from the audit itself (why every finding needs re-derivation)
Three agent calls were wrong, all caught by verifying against source:
"Drop [-]? from cmd_ban's parser" — no-op. %S+ already matches -5 (verified in Lua). The gap was validation, not parsing.
My own first lang test asserted type(v) == "string" → 149 false positives of 151 hits (ucmd_menu*, month_name, pics are legitimately tables); my second enumerated via io.popen("ls") → passes vacuously under a native Windows Lua (cmd.exe has no ls).
Also: an audit agent overwrote the repo's real CMakeLists.txt with a 4-line zlib probe and left a b/ build dir behind. Restored from git; future fan-outs get read-only agents.
Tracker for the dead-code audit (2026-07-16). Scope decided up front: our code only (
core/,scripts/,hub/hub.c, build glue) — vendored deps (lua/,luasocket/,luasec/, …) are report-only. Conservative on contract surface: anything indocs/PLUGIN_API.mdorcore/scripts.lua'sSANDBOX_GLOBALSstays even if unused internally, because third-party hubowner plugins we never see can reach it.Method: 10 parallel agents on disjoint axes, then main-thread synthesis. Every finding below is
file:line-anchored; each still needs re-derivation from source before removal (§1a.4) — the audit already produced three wrong calls, see "Lessons" at the bottom.Already fixed (the audit's real yield so far)
These were not dead code — they fell out while looking:
release.ymlinstalled zlib dev on no leg whilefind_package(ZLIB REQUIRED)is unconditional. The firstv3.2.0tag would have failed the aarch64 build at configure. Merged.etc_cmdlogreadlang.failmsg1/2, keys aremsg_denied/msg_nofile→ German hubs saw English. Second instance of the class, so the guard is now repo-wide (tests/unit/plugin_lang_test.lua, 68 plugins). Found a third (cmd_delreg). Merged.+ban nick X -5storedbantime = -300, the login expiry check pruned it → target kicked once, walked back in, while help promised "ban forever". Merged.core/hci.luais mutable state under shippedcore/→ every upgrade zeroes the hub's lifetime runtime counter. Open.hcimislabelled a dormant module;select()described as the loop after Hub event loop hits FD_SETSIZE=1024 Linux limit (select -> poll port) #310;HTTP_API.mdcitedhttp.lua:logsafe(caps at 80, zero callers) for the 512-byte truncation actually done byhttp_router.lua:logsafe_body. Merged.+ban nick X permanent), raised while fixing fix(cmd_ban): reject a bantime below 1 instead of silently losing the ban #443. Open.PR 1 —
core/hub.luadead file-scope locals (highest value)Why first:
hub.lua's main chunk sits at Lua's 200-local cap, and CLAUDE.md §3 therefore freezes file-scope locals ("any new top-levellocalfails the build"). These 19 are declared and never used — removing them frees 19 slots and un-freezes the file. Smallest diff, biggest structural win.Declared-and-never-referenced (each verified whole-file, matches are the declaration plus comment prose only):
tablesizeUCfield; #179 replaced itloadfileiotable_concatadclib_hashadclib_hashpasadclib_createsaltadclib_hasholdpastypes_checktypes.add/types_utf8are separate and liveutil_difftimekilluserusercountisuseronline_verify,_normal,_protocol,_identify_G_cfg_nick_changenick_changeFEATURE is live (cfg key read bycmd_nickchange/hub_inf_manager) — only hub.lua's stale cache slot is dead_Gremoval trap (would be a #353-class boot crash):local _G(342) +_G = _G(525) must go together. Delete only thelocaland line 525 becomes a bare global write → the restricted env raises "attempt to write undeclared var" at hub load. Delete both or neither.Riding along:
hub.lua:1025-1043isuseronline()— 19 lines, defined, never called, deliberately commented out of the export at 1276. Strict superset of the liveisnickonline/iscidonline/issidonlinetrio.hub.lua:1120-1158killuser— 39 commented-out lines whose only purpose is to justify a dead local slot.hub.lua:310+:1263—hub.usercountis a nil export. Never assigned anywhere in the repo's history (git log -S"usercount = function"is empty), so{ usercount = nil }never creates the key andpairs(hub)never sees it. Any plugin calling it gets "attempt to call a nil value". Not contract surface — a 15-year-old broken stub. 2 lines.PR 2 —
core/doc.lua, whole module (309 lines)Commented out of
_core(init.lua:121--"doc",), itsdoc.export()call site commented out (init.lua:328), its own 19doc.add(...)payload lines sit inside a--[[ ]]block so even if loaded it would document nothing, its output targetdocs/documentation.txtdoes not exist, zerouse "doc"anywhere, not inSANDBOX_GLOBALS, not inPLUGIN_API.md. Untouched since the SourceForge migration.install(DIRECTORY core/)is wholesale so no build change needed.Nuance: it is a deliberate disable (two separate comment-outs), so it is a maintainer call whether to keep it as a dormant design artifact.
PLUGIN_API.mdsupersedes it.Also:
init.lua:202--"test",referencescore/test.lua, which does not exist. 1 line.PR 3 —
core/server.lua(~180 lines; contains judgement calls):1019-1056addclient+:247-290wrapclient+:80socket_tcp— no in-tree caller (the source says so itself at:1025-1028);wrapclientis called only byaddclient.serveris not inSANDBOX_GLOBALSso it is not plugin-reachable. ~85 lines. Judgement call: this is the only outbound-TCP convenience wrapper;http_clientdoes outbound viasocket+server.addtimerinstead. Two latent bugs die with it (ignoredwrapclientreturn →handler = nilon the timeout path; unguardedfailure(...)whenlisteners.failureis nil).:914-955thehandler.starttls/ deferred-TLS branch —starttlsis never called. Inside it:needtls(516/918/950) written never read;handler.receivedata = handler.handshake/dispatchdata = handler.handshake(952-953) assign nil becausehandler.handshakedoes not exist;local shutdown(480) assigned 936/967, never called. ~45 lines. Judgement call: removes the STARTTLS seam. The no-op sub-defects inside are worth fixing regardless of what happens to the block.:1185-1203getsettings/changesettings— exported, zero callers anywhere. ~20 lines.:1214-1216stats— exported, zero callers; cascade:_readtraffic/_sendtraffic(163-164, incremented 708/788) are read only there. ~10 lines. Notehandler.getstats(the per-connection twin,:544) is LIVE (cmd_userinfo.lua:212) — do not confuse them.:633-636handler.pattern+ the deadpatternparameter threaded throughwrapclient/wrapserver/wrapconnection/addclientandp.patternat:1138— no caller ever supplies it; vestige of Phase 8 replacing LuaSocket's"*l"with the iostream pipeline.handler.bufferlen(637-641),handler.id(531-533 + 369-371),handler.dispatch(534-536),handler.disconnect(537-539),handler.socket(401-403). ~20 lines. Live siblings that must not be touched:handler.ssl,handler.getsslinfo,handler.setlistener, the server-handlershutdown,handler.getstats.:125stop(never assigned),:81socket_bind,:66table_remove(tick() callstable.removedirectly),:92+:103-104signal/signal_set/signal_get. 6 lines.:249/:300— thedisconnectbinding inwrapclient/wrapserveris unused (wrapconnection's at:487is live — do not touch).PR 4 — dead unreachable branches (no-ops, but they are landmines)
All confirmed by executing the shapes under the hub's Lua 5.4.8, not by reading:
core/util.lua:927-930+948-951—encode/decode'selsebranches are unreachable (local str = tostring( str )above makesif str thenalways true,tostring(nil)is the truthy string"nil"). Worse: the dead branch readstbl, undeclared in that scope — under the restricted env it would raise "attempt to read undeclared var" instead of returning(nil, err). Disarmed only by being unreachable.util.encode/decodethemselves are contract (PLUGIN_API §5.3) — keep the functions, drop the branches.core/util.lua:638-641, 671-674, 743-746, 747-750— four deadtonumber-shadow type guards (formatseconds,formatbytes,difftime×2). Same shape Fix type check for number in util.lua #439 "fixed":local t = tonumber( t )+if not t then returnguarantees a number, so the type check can never fire either way. Pure no-op deletions. Do not touchconvertepochdate(791-793) /generatepass(702-703) — same shadow but correctly written and reachable.core/adc.lua:353-354— duplicateFC/TOkeys in theSCHnp table (same class as the Refactor bool function in adc.lua #438booldup). Both writes store the identical value, so unlike Refactor bool function in adc.lua #438 this is provably behaviour-neutral. Hygiene only — must not be sold as a fix.PR 5 — small dead locals across the rest (~60 lines, trivially reviewable)
core/cfg.luaPhase-6c extraction leftovers (470, 488-489, 491-496, 528+540 — 11 lines; note removing thetypes_*set also orphanslocal types = use "types", which currently triggers types.lua's load — safe becausecfg_defaults.lua:42does the same, but say so in the PR body) ·core/scripts.lua(31, 43, 56, 59, 87, 330 — 7 lines) ·core/util.lua(90, 104, 130, 136, 140+204, 190 — 7 lines) ·core/hub_dispatch.lua:68·core/hub_user_object.lua:52-53, 293, 632·core/hub_bot_object.lua:34, 94, 311·core/hbri.lua:70+72(+ the now-falsehbri_test.lua:35-38stub/comment) ·core/adc.lua:45, 47, 59, 62, 82+:1127-1128(parse-local shadows of the module-level_contextsend/_contextdirect) ·core/ratelimit.lua:35·core/ipmatch.lua:69, 70, 72·core/blocklist.lua:100+:191+agg.last_seen(235/254, written never read — LRU vestiges) ·core/whitelist.lua:240-247_rebuild_indices(copy-pasted from blocklist, whose only caller is abulk_replacewhitelist deliberately does not have) ·core/http.lua:57/125-131/215logsafe(+ its orphanedstring_sub/string_gsub) ·core/http_events.lua:68/363-364_max_size,:322types_str·core/http_router.lua:66, 73·core/http_filter.lua:52·core/cfg_secret.lua:66, 69, 70, 80+is_active(264-266/320, whose only consumer is itself a dead local atcfg_users.lua:59) ·core/cfg_users.lua:62, 69+74·core/secrets.lua:52·core/sysinfo.lua:34·core/out.lua:16, 35·core/types.lua:14, 15, 39, 40·core/util.lua:535-539(savetblbuilds and sorts atmpthat is never read, then callsiterate(tbl)which rebuilds it — runs on everyuser.tblsave)PR 6 — plugins (~57 lines)
Dead exports not in the PLUGIN_API §8 contract table:
etc_report.lua:73-82+:91broadcast()(whole function; 96 callers usereport.send, zero use.broadcast) ·etc_usercommands.lua:89format·bot_opchat.lua:399bot·cmd_ban.lua:1259bans_path. Plus 17 deadlocal X = lang.Y or "..."locals and their now-orphaned lang keys (40 lines), and 2 never-looked-up lang keys (cmd_reg ucmd_passwort,etc_userlogininfo msg_ccpm_1/2/3).Reported, deliberately NOT removed
usr_uptime.tbl,etc_trafficmanager.add,mmdb.MAX_FILE_SIZE,hmac.BLOCK_SIZE/sha256_bytes,whitelist.reload,util.handlebom,secrets.list_secret_keys,ipmatch.family/normalize,user.getblom,hub_bot_object.lua:252-256bot.reply(the stub must stay — removing it turns a silent no-op into a nil-call crash for unseen plugins; only its 5 commented body lines are dead).luasocket/src/usocket.c:106-118socket_select()— newly orphaned on POSIX by our own Hub event loop hits FD_SETSIZE=1024 Linux limit (select -> poll port) #310 patch;:42-73theSOCKET_SELECTvariant ofsocket_waitfd(macro never defined anywhere);luasec/src/luasocket/— 5 uncompiled.cfiles, but its headers are LIVE (luasec/CMakeLists.txt:29-32puts them on the include path andssl.cincludes them), a latent ODR trap worth its own issue.http_eventshas no unit test at all despite carrying the long-poll deadline logic that already flaked (feat(cfg)!: flip kill_wrong_ips default from true to false #332/fix(http_events): use socket.gettime for long-poll deadline (#263) #334);etc_whitelist._list_filter_spec/_SOURCE_ENUMlack the assertions theiretc_blocklisttwins have.Clean — no findings
Lua 5.1→5.4 migration leftovers: zero (Phase 3 left no compat scaffolding). Unused plugin helper functions: zero of 336. en/de lang-file key drift: zero. Plugins missing from
cfg.tbl: zero. Dead cfg toggles: zero. Read-but-undefined cfg keys in-repo: zero.hub/hub.c: essentially clean (all 7lua_registerglobals live, all 12 statics called). Dead cfg keys:no_cid_taken,ranks,owner_rank,use_ping,op_rank/admin_rank(cfg_defaults.lua731/736/778/4872/768/773) — reachable viaGET /v1/config, soPUT /v1/config/use_pingreturns 200 and does nothing. Always-true validator:ssl_params(cfg_defaults.lua:4439) is the only one of 448 that never rejects — arguably deliberate (nested TLS table), report only.Lessons from the audit itself (why every finding needs re-derivation)
Three agent calls were wrong, all caught by verifying against source:
[-]?from cmd_ban's parser" — no-op.%S+already matches-5(verified in Lua). The gap was validation, not parsing.core/hci.luais a dead stub" — the opposite: live runtime state, and chasing it found hub_runtime: total runtime resets on every upgrade (mutable state lives in core/hci.lua) #445.type(v) == "string"→ 149 false positives of 151 hits (ucmd_menu*,month_name,picsare legitimately tables); my second enumerated viaio.popen("ls")→ passes vacuously under a native Windows Lua (cmd.exe has nols).Also: an audit agent overwrote the repo's real
CMakeLists.txtwith a 4-line zlib probe and left ab/build dir behind. Restored from git; future fan-outs get read-only agents.