The memory-core ratchet (lint-memory-core) failed on the publication
fence commit: it added six raw free() calls, each releasing a block that
existing code allocates with raw calloc, malloc or strdup. cbm_free cannot
release such a block without the core class counters drifting, so every
new exit now reuses an existing release point instead:
- index_supervisor.c: worker_artifacts_create() creates the response, log
and publish fence files and names the one that failed, so all three
failures share the single artifact_create_failed exit.
- pipeline.c: a refused publication gate takes the existing source-changed
abort exit (same CBM_PIPELINE_ABORT_PRESERVE_DB result and cleanup).
- mcp.c: index_request_names_free() is the one release point for the
request name strings, used by index_worker_json_result() and
index_args_serving_index_servable().
Raw-site counts return to the origin/main values (index_supervisor.c 8,
pipeline.c 129, mcp.c 765) with no baseline change. No behaviour change.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Second maintainer refresh of #1561 (truthful index_status freshness),
onto main 9c19b5dd, after the fix-up that keeps the indexed checkout in
its own index_checkout table (a0d0521f). The first refresh is b44a22ca.
Five files conflicted; each is resolved as main plus the branch change:
- src/pipeline/pipeline.c and src/pipeline/pipeline_incremental.c (two
sites): main added .unresolved_calls_complete to the coverage-meta
initializers where the branch added .indexed_checkout_sha. Both fields
are kept, main first.
- src/store/store.h cbm_coverage_meta_t: main added
unresolved_calls_complete where the branch added indexed_checkout_sha.
Both fields are kept, main first.
- src/store/store.c init_schema: main added the unresolved_calls_complete
column where the branch closed index_coverage_meta and created
index_checkout. index_coverage_meta keeps main DDL exactly, and the
index_checkout table follows it.
- src/store/store.c coverage replace: main moved the coverage-meta write
out of cbm_store_coverage_replace_ex into coverage_write_metadata,
whose caller owns the transaction and rolls it back on failure. The
branch wrote or cleared the index_checkout row right after that
write, so its two calls move into the helper: the upsert path returns
index_checkout_write(s, project, meta->indexed_checkout_sha), the
delete path returns index_checkout_write(s, project, NULL).
index_checkout_write stays where the branch defined it, ahead of the
new helpers.
- tests/test_store_nodes.c: main added
store_coverage_shadow_excludes_unresolved_calls and its helper where
the branch added store_coverage_meta_indexed_checkout_sha_roundtrip_and_rollback.
Both tests are kept and registered, main first.
src/pipeline/pipeline_internal.h merged without a conflict: main raised
CBM_SEMANTIC_INDEX_VERSION to 13 for Next.js App Router routes, in a
separate hunk from the branch's cbm_pipeline_indexed_checkout_sha
declaration.
No semantic conflicts:
- The branch bumps no version. index_checkout is created by init_schema
with IF NOT EXISTS and read only when present, so main's semantic
index version 13 applies unchanged.
- The branch changes no existing function signature, and main changes
none that the branch calls.
- Every main caller of cbm_store_coverage_meta_get added since the
merge base, including the new Next.js upgrade test in test_pipeline.c,
releases the result with cbm_store_coverage_meta_clear exactly once,
which frees the checkout string. That test passes a shallow copy of
the result to cbm_store_coverage_replace_ex and never frees the copy
on its own.
No test call site needed adapting.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Removing the only Hermes pre_llm_call hook empties the hooks mapping, and
the mapping editor writes an emptied section as hooks: {} - the same
convention it uses for mcp_servers: {}. The hook sequence editor then
refused that file for both upsert and removal, because it required every
parent on the sequence path to be a block mapping header. On a config
where our hook was the only entry under hooks, install -> uninstall ->
install exited 1, and a repeated uninstall did too.
An exact empty flow mapping on a parent of the sequence path is now read
as that empty mapping, mirroring how the mapping-entry editor already
treats section: {}. Removal is a byte-identical no-op, and upsert expands
the header in place, keeping its comment, the line endings and every
other byte, so a reinstall lands back on the first install byte for
byte. The sequence key itself holding a mapping, any other flow value,
and content nested under the inline mapping stay refused. Hooks written
by the user and every foreign key are left untouched.
Found while finishing #1862: its persisted hook opt-out runs this remover
on every later install, so every Hermes user whose only hook was ours
would have hit the refusal on each install after the first.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to main 9c19b5dd.
Two same-spot insertion conflicts, each resolved as the main version
of the file plus the own diff of this branch:
- src/daemon/application.c: the job struct gained watch_owners on main
and request_started_ms plus the request facts here; both kept.
- tests/test_daemon_application.c: main added the #1818
autoindex-skipped helpers where this branch added the worker resource
failure test; both kept, the test placed after the doc_candidates
test.
Everything else merged cleanly. src/main.c changed on both sides: main
(#2838) lowers the worker CPU priority inside the worker role block,
and this branch strips the publication fence flag before that block
and installs the fence after the worker role options; the hunks are
disjoint and both are kept. tests/test_pipeline.c gained the Next.js
route tests (#2747) on main and the publication gate test here, also
disjoint. No call site needed adapting: no new main code calls a
function this branch re-signed, and main re-signed none that this
branch calls.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
index_coverage_meta is one of the tables the graph digest pins column
by column, so the new indexed_checkout_sha column made the digest
reject every live store with a schema error: the live-schema
compatibility test failed, and test-impact, which digests the graph it
builds for HEAD, fell back to running every test for every change.
Record the checkout in a table of its own instead: index_checkout
(project primary key referencing projects ON DELETE CASCADE,
indexed_checkout_sha NOT NULL). A checkout is source identity, which
the graph digest deliberately does not bind, and the store already
keeps other per-project data (doc_link_*, observed_*) outside the
digested tables. index_coverage_meta keeps exactly the shape the digest
expects, so the digest needs no change and no version bump, and a new
table needs no migration of existing databases.
The row is written and cleared in the same coverage transaction, so a
failed replace keeps the previous identity, and it is removed with its
project. The store API is unchanged: cbm_coverage_meta_t carries
indexed_checkout_sha as before. Read-only opens of databases without
the table report no identity, as before.
store_graph_digest_indexed_checkout_identity_is_not_bound writes a
store through the real coverage API with and without a checkout SHA
and asserts that it still digests and that the SHA leaves the digest
unchanged.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A worker resource limit is enforced by the supervisor, but only the worker
replaces the serving database, and it does so before the persistence
artifact export. A limit observed in that window stopped a worker that had
already published, and the response still said serving_index_preserved=true
while the previous index had been replaced.
Publication and resource termination now coordinate through a per-attempt
publication fence: a never-created path next to a reserved unique file in
the private worker directory, which both sides claim by creating it
exclusively (cbm_fopen "wbx": O_EXCL on POSIX, CREATE_NEW on Windows), so
exactly one of them can win.
- The worker claims it through a new pipeline publication gate that
cbm_pipeline_run calls once, after the last pre-publication check and
before the serving database is touched. Losing means the supervisor
already stopped the attempt for a limit: the run aborts before
publication and the previous generation stays in place.
- The supervisor claims it before it terminates the worker for a limit or
a failed RSS probe. Losing means the worker already committed to
publishing: the limit is no longer enforced for that attempt, which runs
to completion, and index.supervisor.resource_limit_after_publication is
logged instead. A claim that can be neither won nor lost is retried on
the next poll rather than risk stopping a committed worker.
- At terminal the supervisor settles the fence into
result.publication_committed. An unquiesced tree keeps the fence, so a
surviving member can never publish after the report.
serving_index_preserved now describes the request, not only its last
attempt: a servable index existed when the request began, no attempt of
the request committed to publishing (an earlier attempt can publish and
then crash into recovery), and that index is still servable. The direct
path and the daemon job both fold every attempt into these request facts;
cbm_mcp_index_worker_resource_response takes them as a third argument, and
NULL claims nothing.
The fence path travels as an optional last pair of the exact worker grammar
(--index-worker-publish-fence), accepted by the worker argv parser and by
the daemon bootstrap role check, stripped by the CLI and installed by both
worker entries.
Limits stay opt-in: without index_max_rss_mb or index_max_duration_seconds
there is no fence, no extra argv, no claim and no extra database read.
Graph content is unchanged either way.
RED (c78c077a plus only the new end-to-end test): a FIFO at the artifact
temp path holds the real worker after the rename, and the duration clock
crosses its limit only once the serving database has been replaced. The
supervisor stops the worker there and answers resource_limit_exceeded with
serving_index_preserved=true over a replaced database. GREEN: the worker
claimed first, the supervisor logs the limit as not enforced, and the
released worker finishes and reports the published index.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cppcheck 2.20 (the CI version) reports shadowFunction: the local
cache_dir in cbm_mcp_ignore_worktrees_runtime_enabled() shadows the
static cache_dir() helper in mcp.c. Rename the local to store_dir; no
behavior change.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to main a5b248f47. Eight files conflicted, each a
same-spot insertion where main and this branch added lines next to the
same anchor. Each resolves to the main version plus the change of this
branch:
- src/cli/cli.h, src/cli/cli.c: CBM_CONFIG_IGNORE_WORKTREES and its
CONFIG_KEYS row next to the new doc_candidates key.
- src/mcp/mcp.c: the index_worktree schema property next to the new
doc_candidates property.
- README.md, docs/CONFIGURATION.md: the ignore_worktrees line and table
row next to the rewritten auto_watch entries and the new
doc_candidates and watch_non_git rows.
- src/git/git_context.c: the env-isolation comment after the new debug
stage marker, directly above cbm_popen_git.
- src/cli/hook_augment.c: the new SubagentStart cli_hint ahead of the
parent-graph check of this branch.
- tests/test_mcp.c: both RUN_TEST blocks; main already includes
str_util.h.
Per file, the lines changed against main equal the changes of this
branch against the merge-base.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The review of 2026-10-01 asked for this to be a stated decision rather
than a side effect. Detection walks up from the given path to the
nearest .git entry, so a subdirectory of a linked worktree is treated
as that worktree: index_repository on it is refused, and a session root
or hook working directory inside it is handled the same way. The walk
stops at the first .git; outside any checkout it runs to the filesystem
root one directory at a time. None of it runs while the key is off.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The lifecycle hook now reads ignore_worktrees first and does nothing
else for it while the key is off; the project lookup is then the one
the hook did before the key existed.
- cbm_mcp_ignore_worktrees_runtime_enabled() replaces
cbm_mcp_server_attach_runtime_config(). It never attaches a store to
the server, which may be the daemon session server. A server with a
store answers from it. Otherwise the runtime store is opened only to
read this key, and only when it already exists, so a hook event never
creates _config.db.
- cwd canonicalization, the linked-worktree probe and the parent-graph
check moved into ha_resolve_project_ignoring_worktrees(), which runs
only when the key is on.
Test cli_hook_ignore_worktrees_default_off_does_no_worktree_work was red
on the previous head (a default-off event created _config.db). It also
pins, through a test-API counter, that neither an absent store nor a
store without the key reaches the worktree gate, and that the key on
reaches it once. mcp_ignore_worktrees_runtime_read_leaves_server_and_store_alone
replaces the test of the removed helper.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The review of 2026-10-01 asked to split these out of the ignore_worktrees
change. Neither belongs to it, and both are already on main in another
form, so this restores the three files to the branch merge-base:
- Makefile.cbm: main links cbm-fuzz with -fsanitize-link-c++-runtime.
- src/cypher/cypher.c and tests/test_cypher.c: main frees a partially
parsed MATCH pattern since 70543ba82, with its own regression test
cypher_parse_error_frees_partial_pattern.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to date with main (a5b248f4). No conflicts.
scripts/test.sh keeps Step 0e4 next to Step 0e3; the gate script and
its test are unchanged by main.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The test only covered a whitelisted finding. The defect in #2442 is
the other direction: on main clang-tidy reported findings that carry
the ,-warnings-as-errors suffix, the old FINDING_RE matched none of
them, and the gate reported clean. Pin that direction too: a second
fixture function with no whitelist entry, a suffixed finding inside
it, and the gate must exit 1 and report the finding under its plain
check name ([clang-analyzer-core.NullDereference], the whitelist key
form) as an entry missing for unlisted().
With the suffix group of FINDING_RE reverted, this case exits 0 with
"memory gate clean" and the test fails, naming all three assertions.
A parser that accepts the line but keeps the suffix inside the check
name fails it as well. The negative assertion is a maintainer
decision (R1).
Refs #2442
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
scripts/test.sh lists every shell contract test explicitly and does
not glob tests/, so tests/test_lint_mem_gate.sh never ran on any leg.
Register it as Step 0e4, next to the other gate contract (Step 0e3),
in the same two-line shape as its neighbours. Step 0 runs in every
default scripts/test.sh leg (macOS, Linux, Windows) and in the PR
contracts-only job. The wiring, Windows included, is a maintainer
decision (R1).
Make the test portable to the Windows leg. There python3 is a native
MSYS2 build: MSYS converts path arguments for it, but never the
diagnostic text on stdin, so the absolute fixture path inside the
finding does not resolve there, and a C:/ path would not parse (the
file field of FINDING_RE ends at the first colon). The gate now runs
from inside the fixture directory, in a subshell, with relative paths.
CRLF from a native python3 is stripped from the --hash output, the
whitelist is written once the hash is known (no placeholder rewrite),
the gate exit status is asserted, and every failed assertion is
reported together with the gate output.
Refs #2442
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cbm_subprocess_tree_rss_bytes skipped any non-root process it could not
measure and still returned OK with a smaller total, so a descendant the
platform refused to measure silently dropped out of the RSS watchdog.
Now a member is skipped only when the platform says it no longer exists:
ESRCH from proc_pid_rusage on macOS, ENOENT or ESRCH for /proc/<pid>/stat on
Linux, ERROR_INVALID_PARAMETER from OpenProcess (or an exit code from a
handle whose memory query failed) on Windows. Any other failure fails the
probe, which the supervisor already turns into resource_probe_failed after
three consecutive errors. The root stays strict as before.
On macOS the members now come from proc_listpids(PROC_PGRP_ONLY): the old
all-process scan read every process with proc_pidinfo, which answers EPERM
for every process of another user (178 of 6438 on this machine), so it could
not tell a foreign process from an unreadable member and fail-closed
semantics would have failed every probe there. Linux keeps its /proc scan;
stat files are world-readable on a default mount, and a hidepid=1 mount now
fails closed, which the doc states.
A test seam makes every non-root measurement fail with one platform error.
RED (old skip-any-failure decision): the live-member test returns OK where
ERROR is required. GREEN with the fix; the exited-member test passes in both,
so verified exit races are still ignored.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
subprocess_tree_rss_measures_contained_descendants asserted only that the
tree total was above zero, which the root alone satisfies, and it was
skipped on Windows although its skip text said it ran there.
The test runner gains two roles: __cbm_rss_tree_root starts one
__cbm_rss_tree_hold descendant (fork on POSIX, CreateProcessW into the same
Job Object on Windows) that touches a controlled allocation and publishes its
pid by an atomic rename. The test measures two otherwise identical trees, one
whose descendant holds nothing and one whose descendant holds 128 MiB, and
requires the second total to be at least 96 MiB higher. Only a probe that
measures past the root can pass. The test now runs natively on Windows.
The waits have no deadline: they end when the descendant is ready or the
tree stops running, and the root exits when its descendant dies.
Binding check: with the macOS probe reduced to the root only, the test fails
(the loaded tree is not 96 MiB above the idle one). GREEN on the real probe
(idle 43 MiB, loaded 187 MiB under ASan).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
index_supervisor_quiet_timeout_remains_hang_with_duration_enabled ran the
duration watchdog on the real clock with a 60 s limit next to a 1 s quiet
timeout. On a starved host the duration limit can be crossed before the
quiet timeout fires, and the verdict then turns from HANG into a resource
kill, so the test depended on how fast the machine was.
The resource clock is now pinned at the request origin through the existing
clock seam. The quiet timeout runs on the subprocess clock, so it is the only
way the hung worker can end, however slowly the host runs. The limit is set
equal to the quiet timeout on purpose: more real time than the limit always
passes and it still cannot fire. A new assertion checks that the duration
check read the pinned clock, so HANG wins against an armed watchdog.
RED (real clock, limit crossed first): FAIL ASSERT(remained_hang) with
index.supervisor.resource_limit resource=duration_ms. GREEN with the pin.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings #2198 (benchmark harness runtime isolation, #1696) up to date
with main a5b248f4.
No conflicts. Of the files this branch touches, main changed only
scripts/test.sh since the merge base 8530c8cd: it adds Step 5-alloc
(production allocator contract), far from this branch's Step 0i3,
which is unchanged. scripts/test-runtime.sh, which the harnesses and
their contract test source, is unchanged on main. No caller of either
harness outside docs/EVALUATION_PLAN.md exists on main.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three more cases in the benchmark runtime isolation contract, each RED
against the harness without its fix:
- a stale runtime-root.txt is gone after a run that fails during setup
(a repository path that does not exist);
- with the keep flag and a `daemon stop` that refuses, the run fails,
writes no handoff, keeps its root and prints the refusal; the test
removes that root itself, before any verdict, so a failing assertion
leaks nothing;
- a refused `daemon start` fails both harnesses, prints the cause and
leaves no root behind.
The fixture refuses the start on stderr and the stop on stdout, the
channel the product uses for a busy refusal.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Section 7 still began with rm -f ~/.cache/codebase-memory-mcp/*.db.
Every index in the loop now lands in a run-private cache (#1696), so
that line no longer made any run colder; it only deleted the indexes in
the live store of the operator. Dropped, and the comment above it now
names only what the setup still does.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
With CBM_BENCH_KEEP_RUNTIME=1, bench_finish ran `daemon stop` with its
output and exit status discarded and wrote runtime-root.txt regardless.
`daemon stop` refuses while a committed client still uses the daemon and
fails when the daemon does not answer; the handoff then promised a
stopped daemon that was still running.
A refused stop now prints the explanation of the product, writes no
handoff, keeps the root without cleaning it up (the daemon may still be
using it) and exits non-zero. A stop with nothing to stop still exits 0,
so a clean run is unaffected.
Section 7 step 8 of the evaluation plan follows the same rule: the kept
root is removed only if its daemon stopped, and is kept with a message
otherwise.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Both harnesses discarded the output of `daemon start`, so a refused
start was reported only as "private daemon did not start", the symptom
without its cause. The output now goes to a run-private file next to the
index stderr, the shape both scripts already use for the index call, and
is printed under "--- daemon start output ---" when the start fails.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
benchmark-index.sh removed the runtime-root.txt of an earlier run only
after the repository path, the private runtime and the temp directory had
been set up. A run that failed in one of those steps exited non-zero but left
the previous handoff in the reused results directory, and section 7 of
the evaluation plan sources that file unconditionally, so the graph
session would be pointed at a root that step 8 has already removed.
The results directory is now created and the stale handoff removed right
after argument parsing, before any setup step that can fail.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The recovery path replays the session context and UI settings on the
replacement daemon. It waited on the bootstrap connect budget, which only
bounds a probe (1 s in production), while main.c gives the first
set_context and set_ui_config the startup budget since eb294298e. A
replacement daemon whose UI save runs longer than a probe would end an
otherwise good recovery.
frontend_configure_client now waits on startup_timeout_ms. A test seam
records the budget the replay used, and the recovery fixture gives the
bootstrap distinct connect and startup budgets and exits 75 when the
replay waited on the wrong one. With the old budget exactly the three
recovery tests fail with exit 75; with the fix daemon_frontend is 17/17.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Point CBM_DOWNLOAD_URL, the documented download-base override in
install.sh, at a closed loopback port. If a curl stub were ever missed
on some leg, the download would then fail locally instead of fetching
a real release.
Check that the uname stub is the one PATH resolves before running
install.sh. Otherwise the test would quietly exercise the real host
instead of an Android one and prove nothing.
Refs #1202
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to current main (it was 2061 commits behind) so
the new Step 0x2 runs inside the current scripts/test.sh. No conflicts.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The guard told users to build from source with scripts/build.sh in
Termux, which reads as a supported path. Source builds under Termux
still have open problems tracked in #1202, so the message now says
why the portable binary cannot run there and that a source build may
work but is not supported yet, pointing at #1202.
The test still matches Android/Termux in the new message.
Refs #1202
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
scripts/test.sh lists every shell contract test explicitly and does
not glob tests/, so an unlisted test never ran on any leg. Register
tests/test_install_android_guard.sh as Step 0x2, right after Step 0x,
in the same two-line shape as its neighbours.
Give the two PATH stubs (uname, curl) the same shebang every other
stub under tests/ uses, so they are proper executables on every leg
instead of relying on the shell fallback for files without one.
Refs #1202
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Every shell contract test under tests/ is named test_*.sh with
underscores. A pure rename, so history and blame follow the file.
Refs #1202
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to date with main edc2301b. Main only changed
CI configuration since the branch base (CodeQL action, actions/attest
and msys2/setup-msys2 pins, Dependabot grouping); no conflicts.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A construction call now resolves to __init__ only when the called
class declares that __init__ itself. An inherited __init__ is no
longer followed: class B(A) without its own __init__ keeps the B()
call on the Class B, as it was before this branch.
Following the bases bound every subclass construction to the base
constructor. On a django index (fb113765) that made Model.__init__
a 583-caller hub (every model instantiation), bound IntegerField()
to Field.__init__ and HttpResponseForbidden() to
HttpResponse.__init__, and degraded 625 constructor call sites
versus main (457 fell back to textual resolution, 168 lost their
row; CALLS -161). With own __init__ only, 6016 sites move to the
class own __init__, 5468 stay on the Class, none fall back to a
textual strategy, and total CALLS is +4 versus main.
Tests: the same-file and cross-file inherited cases now pin the
Class target, and a new same-file subclass test at the LSP and the
graph level covers B(A) without __init__ (stays on B, never
A.__init__) and C(A) with its own __init__ (binds to C.__init__,
never the class C or A.__init__). The strategy inventory comment in
the repro suite is updated to match.
Refs #1642
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The REG_* macros wrote through the return-type vector without checking
the allocation, and registered entries whose formatted QN came back NULL.
A shared ruby_seed_signature helper now leaves a failed vector out, so the
signature types as unknown (the fallback ruby_register_method already
uses), and REG_METHOD / REG_SMETHOD skip an entry whose formatted name is
NULL, since a NULL QN would reach strcmp in finalize and in every lookup.
rubylsp_stdlib_seed_survives_alloc_failure seeds a registry from an arena
that cannot allocate. Before: UBSan reported a store to a null pointer and
the run crashed with SEGV at ruby_stdlib_data.c:163. After: every
registered entry carries a name, the registry finalizes, and the Kernel
built-ins with literal names still resolve.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
ruby_const_path recursed once per :: segment and re-formatted the growing
prefix at every level, so an A::B::C path cost one stack frame per segment
and quadratic bytes in the per-file arena. It now follows the scope fields
in a loop: one pass measures the segments, a second copies them right to
left into a single buffer. The result is the same string as before,
including a leading ::Foo and a scope that is not a constant path.
rubylsp_const_path_is_linear pins the arena bytes for an 800 vs a 1,600
segment path (before: 494,520 -> 1,660,520 bytes already at 400 -> 800
segments; after: 136,248 -> 150,848 at 800 -> 1,600) and the exact class
QN for a five-level path and for a rooted ::Top::Inner.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The mixin walk recursed into prepend and include at every level with only
the depth cap of 16 as a bound, so a module that both prepends and
includes the next one doubled the paths per level: a miss on a chain of n
such modules cost 2^n visits (measured 4 -> 20,744 for n = 2..14).
Each lookup is now one walk that carries the fail-closed blocked flag and
a visit stamp. Ruby linearizes ancestors, so a module reached a second
time in one walk was already searched and missed (a hit or a block would
have ended the walk); skipping the repeat keeps every lookup linear in the
ancestors the file declares. Lookup order, hits and the fail-closed rules
are unchanged. The stamps live on the per-file class table, so the walk
allocates nothing.
The walk entries feed CBM_LSP_WORK (seam builds only; product builds
compile it to nothing). rubylsp_mixin_walk_visits_each_module_once pins
linear work for the doubled chain on the instance and the class side
(before: 66 -> 4,098 visits for n = 6 -> 12; after: 14 -> 26) and keeps a
diamond and a prepend-plus-include pair resolving exactly once.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Main already moved upload-sarif to v4.39.1 (#2779). Move init and analyze to
the same commit, 9b3254085494b55a0f7c4c2b3d2d52cc712ce370 (the commit the
v4.39.1 tag points to), so all three CodeQL actions run one version again.
The pin comment follows the form Dependabot wrote for upload-sarif. The
codeql-action Dependabot group added earlier on this branch keeps the three
in lockstep from here on.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Conflict in .github/workflows/scorecard.yml resolved to the main version:
main already pins upload-sarif to 9b3254085494b55a0f7c4c2b3d2d52cc712ce370
(v4.39.1, #2779), which supersedes this branch's v4.39.0 pin.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The V2 compaction hook pushed a plain object into the compaction request's
event.messages. OpenCode's Message is a schema class at v2.2.5, and nobody has
run this path on a real 2.x host. The try/catch only guards the array push,
which cannot throw. If OpenCode rejects the object, that happens inside its own
compaction, out of the plugin's reach, so an untested note could break
compaction for every OpenCode 2 user. Losing the note costs far less.
Remove the V2 compaction registration and the sessionHooksRegistered flag it
needed. The V2 tool hook, proven on OpenCode 2.2.5, stays, and so does the
1.18.x experimental.session.compacting path. OpenCode 2 sessions still get the
session note on the first tool result of a session. A later change can add a
note after compaction together with a real 2.x run.
The replacement test pins that the generated module registers no session hook
and touches neither event.messages, a system property nor an instruction entry.
Refs #2204
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The hosted Ubuntu x64 runners read the archive through
mirror+file:/etc/apt/apt-mirrors.txt. With Acquire::Retries above 0
(including the apt default of 3), a failover from a dead first entry
of that list (connection refused, DNS failure or a stalled host) to
the next entry goes idle: apt-get fetches a few InRelease files from
the second mirror and then waits until the step timeout. With
Acquire::Retries=0 the same failover completes. Seen on noble
(apt 2.8.3) and jammy (apt 2.4.14).
Every apt step this branch hardened now picks the retry count at
runtime, applied to both update and install: 0 when the runner has the
mirror list (moving to the next mirror is the retry), 3 otherwise
(arm64 runners and any host with plain sources keep their retries).
--error-on=any, the http and https timeouts and the step timeouts are
unchanged.
Proof: the step text extracted from the edited _build.yml (update plus
install of zlib1g-dev), run under bash -e in ubuntu:24.04 and
ubuntu:22.04 containers with runner-shaped mirror lists:
- first mirror refused: exit 0 in 8 s / 9 s, every index and deb
fetched from the second mirror
- first mirror stalled (192.0.2.1): exit 0 in 68 s / 69 s, one 30 s
timeout each for update and install, then the second mirror
- healthy first mirror: exit 0 in 7 s / 9 s
- no mirror list, plain sources: the prefix keeps Retries=3, exit 0
in 8 s / 7 s
- every mirror dead: update exits 100 in under 1 s, install skipped
- revert check, the previous step text with a dead first mirror
(refused or stalled): idle with no Packages index at the 180 s cap
on both releases
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
apt-get update exits 0 when index fetches fail: it only prints
"W: Some index files failed to download. They have been ignored, or
old ones used instead." and the job breaks later at install time with
a misleading "Unable to locate package", or installs from stale lists.
Add --error-on=any to every apt-get update this branch hardens, placed
after the -o options as in test-infrastructure/Dockerfile.msan, so the
update step itself fails when an index cannot be fetched.
Proof in ubuntu:24.04 (apt 2.8.3) and ubuntu:22.04 (apt 2.4.14)
containers with a runner-shaped mirror+file:/etc/apt/apt-mirrors.txt
source and the same -o options CI passes:
- every mirror unreachable: exit 100 with the flag (E: lines), exit 0
without it (W: lines, zero Packages lists), on both apt versions
- first mirror refused or stalled (30 s connect timeout) and the real
archive second: the Ign: failover lines do not trip the flag, exit 0
with every index fetched (measured with Acquire::Retries=0)
- the image sources with the flag: exit 0 and no Ign/W/E lines, so the
option is understood by apt 2.4 and 2.8
- green hosted-runner logs (ubuntu-24.04 and ubuntu-22.04 legs) show no
Ign/Err/W/E lines in the update step
With Acquire::Retries=3 a mirror failover leaves apt idle after the
InRelease failover, with or without the flag and also for a plain
apt-get update; that apt behaviour predates this change and is
unchanged by it.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Tool.Result allows content: [], and for it the array branch of
appendResult pushed a text part that held only the session note, so the
note became the whole content of the tool result. Add a text part only
when the array already holds a part; an empty array now falls through to
the string output branch, or the result is returned unchanged.
Pin it in a new agent_clients test: the part-adding push sits inside the
non-empty array branch, ahead of the string output fallback, and no
unguarded array branch is left.
Refs #2204
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The OpenCode 2 compaction hook receives the compaction request, whose
messages are the input the summary is built from. Add the post-compaction
session note there as a text message, mirroring the 1.18.x path that
pushes it into output.context. Session instruction entries are rendered
into the system array of every later request, so the note no longer goes
through ctx.session.instructions.entry, and event.system stays untouched.
Registration and the push still fail open.
Pin it in a new agent_clients test: the push sits inside the compaction
registration, carries a user text part, is guarded by an array check, and
the generated module names neither a system property nor an instruction
entry. The #2077 test drops the assertions that pinned the old channel.
Refs #2204
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
On 2026-11-01 two Linux jobs hung in `sudo apt-get update` right after
"Get:5 https://archive.ubuntu.com/ubuntu noble-security InRelease" until
their job limit; the build never started in either:
- PR 2641, test-tsan x86 (run 41251045506, job 124688326533): the
"Install deps (Ubuntu)" step ran 2 h until the 120-minute job limit.
- PR 2395, test-unix gcc x86 (run 41256855177, job 124705168264): the
same step ran 4 h until the 240-minute job limit.
Both logs show the azure mirror ignored after about 15 s, the fallback to
archive.ubuntu.com, and then no output until the cancel. The default apt
http timeout (120 s) did not end the stall.
Rule applied to every workflow step whose run block calls apt:
- A step-level timeout-minutes. 10 for steps that only provision packages
(green runs on 2026-11-01 and the last green dry run: 5 to 76 s); 20 for
the three pr.yml steps that also run scripts/build.sh (green: 246 to
331 s). A stalled mirror now fails the step in minutes instead of
holding the runner until the job limit.
- `-o Acquire::Retries=3 -o Acquire::http::Timeout=30
-o Acquire::https::Timeout=30` on every apt-get update and install,
with the -o options before the subcommand as in
test-infrastructure/Dockerfile.msan.
Nothing else changes: no action, runner, package or step-order changes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>