Skip to content

Navigation Menu

Sign in
Sign up

Enable bounded Ubuntu long-soak evidence - #82

Draft
test1card wants to merge 78 commits into
master from
codex/ubuntu-long-soak-readiness
Draft

Enable bounded Ubuntu long-soak evidence #82
test1card wants to merge 78 commits into
master from
codex/ubuntu-long-soak-readiness

Conversation

@test1card

@test1card test1card commented Aug 18, 2026
edited
Loading

Copy link
Copy Markdown
Owner

Purpose

Make the existing whole-stack soak runner usable for bounded 12-hour, 72-hour, and 168-hour Ubuntu 22.04 laboratory-readiness evidence.

This pull request adds no laboratory completion claim. It prepares the evidence path so a later run can measure continuity, persistence, resource growth, restart behavior, and final verified-OFF state on one frozen commit.

Why this is needed

The repository defined long profiles, but the production runner refused them. The prior path could not produce the evidence needed for a week-long thermal-conductivity campaign.

The correction keeps validation bounded. It verifies the exact runtime library set, avoids loading the full 168-hour sample history into memory, checks native-library identity across restart boundaries, and aligns the laboratory checklist with Ubuntu 22.04 profiles.

Exact candidate evidence

Candidate: 2198fe4

On the exact candidate in CryoDAQ-Lab-Ubuntu-22.04:

  • full repository suite: 10,719 passed, 50 skipped, 1 deselected;
  • exit status: success;
  • elapsed time: 2,616.76 seconds;
  • independent cold breadth review: CLEAN;
  • review report SHA-256: 294f9cdd5c1b3ebc623dc07a2ccd3bf2112f231dd2f8a8762409c5870c9fea40.

The skipped tests remain environment-gated. No physical instrument, dummy-load, 12-hour, 72-hour, or 168-hour result is claimed here.

Merge boundary

Keep this pull request draft until the exact-head Codex verdict is clean and every required hosted check succeeds. A later physical or duration run must use one frozen commit and retain its own immutable artifacts.

test1card added 30 commits August 18, 2026 01:34

Copy link
Copy Markdown
Owner Author

The diagnostic paid for itself in one round

At the pushed head fa0fcb12be, on Ubuntu 22.04, in a clean native worktree.

One round ago the refusal said only:

source stack did not reach the exact four-role startup cut

Four separate conditions had to hold and the message named none of them. It now
says:

source stack did not reach the exact four-role startup cut;
missing: roles;
_load_roles refused: ValueError: unclassified descendant process

That is the third time on this branch that fixing the diagnostic before
chasing the cause produced the cause immediately.
The first was the engine's
RuntimeError with no message; the second was the whole runner exiting 1 with
zero bytes on both streams.

What it means

_load_roles walks the launcher's process tree and requires every descendant to
be one of the four known roles. There is a fifth process it cannot classify.

From the launcher log the tree contains at least the launcher, the ZMQ bridge
subprocess, the engine subprocess and cryodaq-assistant. So something else is
there.

Why this matters beyond the soak: a process the launcher cannot classify is
a process it cannot settle or kill. A week-long run that leaks one per restart
accumulates them, and that is the memory-and-descriptor growth the whole
endurance effort exists to rule out. If the check is right, the fix is
ownership, not the check.

Also visible in the same log, and not yet a finding

ERROR │ cryodaq.launcher │ Launcher shutdown owner remains unsettled: engine
 (RuntimeError: engine process has not yet exited after a verified
 shutdown receipt; launcher remains in HOLD pending exact process exit)

The launcher receives its shutdown receipt from the engine and the engine
process still does not exit. That may be the same story as the unclassified
descendant or a separate one; it is recorded here so it is not lost, and it will
be judged on evidence rather than folded into the current round.

What is running now

A worker names the unclassified process — identifier, parent, and command line,
because "there is a process" is not actionable — then reads how a role is
matched and whether any allow-list exists. It is told to add nothing to an
allow-list in this round, to state its hypothesis AS a hypothesis, and to say
what would settle it.

The plausible sources, none established: a Qt platform helper on a headless
host; a shell interposed by the spawn path; a multiprocessing helper from the
start method this branch changed one round ago; or a genuine leak.

Copy link
Copy Markdown
Owner Author

Master is RED on the target platform, and this branch is what fixes it

Measured on Ubuntu 22.04.5 LTS, Python 3.14.6, in clean worktrees cut from the
native Linux clone, each with the --system-site-packages environment the
runner requires.

head tests/scripts/test_soak_mock_stack_runner.py -k strict_exact_six
master 4e3fa39066 2 failed
this branch fa0fcb12be 2 passed

The failure master carries

_RunnerFoundationError: exact-six execution failed: exit code 4; captured stdout:
ImportError: /lib/x86_64-linux-gnu/libstdc++.so.6: version `CXXABI_1.3.15' not found
(required by .../miniforge3/envs/cryodaq-lab/lib/python3.14/lib-dynload/../.././libicui18n.so.78)

The child loads the system libstdc++ together with the conda
environment's
libicui18n, and the two do not match. Measured directly:

  • .../envs/cryodaq-lab/lib/libstdc++.so.6 -> libstdc++.so.6.0.35, and
    CXXABI_1.3.15 appears in it twice;
  • /lib/x86_64-linux-gnu/libstdc++.so.6 contains it zero times.

The library the child needs is present in the environment and is not the one it
loads.

Why this branch fixes it

Master computes the controlled LD_LIBRARY_PATH from sys.prefix. Under the
virtual environment the runner itself demands, that directory holds no shared
objects at all, so the child falls back to the system loader path and finds the
wrong libstdc++. This branch computes it from sys.base_prefix when the two
differ, which is the environment that actually holds the interpreter's libraries.

That change was made for a different reason — a stock virtual environment
produced an empty native-library closure — and it repairs this as well.

What it means for the queue

Every open pull request inherits this red node, including ones that touch no
code at all: #84 changes only ROADMAP.md and shows exactly the same failure.
Anyone chasing it inside another branch is chasing master's defect.

It is therefore not an acceptance to be written per branch. It is one fix, here.

Landed by the batch lander. The lane's own report and the coordinator's
verification are recorded on the pull request.

Copy link
Copy Markdown
Owner Author

@codex review

Head is 5c1335b791180ea607613c0543f63ef6acfab935. A lane closed the live review findings; the coordinator ran the landing gates on this tree -- byte-order-mark, encoding and parse checks on every changed file, a refusal on any tree that deletes more than it adds, ruff check and ruff format --check on the changed Python, the derived documentation pair regenerated to a fixed point, and the documentation gate green.

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Landed by the batch lander. The lane's own report and the coordinator's
verification are recorded on the pull request.

Copy link
Copy Markdown
Owner Author

@codex review

Head is 29c6e3ea0fbe0c394800bfbabb54ce3460d336b2. A lane closed the live review findings; the coordinator ran the landing gates on this tree -- byte-order-mark, encoding and parse checks on every changed file, a refusal on any tree that deletes more than it adds, ruff check and ruff format --check on the changed Python, the derived documentation pair regenerated to a fixed point, and the documentation gate green.

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown
Owner Author

The soak RUNS. It sampled, it injected a fault, and the fault recovered.

Measured at the pushed head 29c6e3ea0f, on Ubuntu 22.04 in a clean native
worktree. This is the deepest the run has ever reached.

  • The four-role startup cut is passed — the resource-tracker recognition
    from the previous round closed it.
  • The run sampled: samples.jsonl holds 38 records and
    runtime-closures.jsonl holds 4. Neither file had ever been written on
    any earlier head.
  • Fault injection worked, and so did recovery:
    ZMQ bridge replacement committed; reason=data-flow generation=1 restart_count=2 pid=1855.
    A role was killed on schedule and came back.

It then stopped at:

_RunnerFoundationError: faulted child did not recover within the reviewed ceiling

Where the barriers have gone

barrier state
interpreter and its libraries cleared
missing theme pack cleared
silent refusal, zero bytes cleared
dead safety pattern cleared
incomplete physical alarms, no set validator cleared
four-role startup cut passed
bridge process ownership cleared
unclassified descendant (multiprocessing.resource_tracker) cleared by exact recognition
a faulted child not recovering current

The round now running is about the class, not the instance

The refusal says a child did not recover and does not say which. That is the
fourth message on this branch that names a condition without its subject, and
every previous one produced the cause on the very next run once the subject was
named.

So this round names this child AND sweeps both soak modules for every refusal
that names a condition with no role, path, identifier, or measured value against
its bound — reports the list with counts, then fixes them in the same shape. It
is the same move as the isolated-config-set validator: fix the class so the next
round is about the soak rather than about reading its mind.

Diagnostic only. No bound moves, no condition is relaxed.

Recorded, not chased

The launcher also repeats at shutdown:

Launcher shutdown owner remains unsettled: engine
(RuntimeError: engine incarnation lacks exact shutdown settlement;
 launcher remains in permanent HOLD)

That happens at SIGTERM, after the failure, so it may be a consequence. It is
written down rather than folded into the current round.

Landed by the batch lander. The lane's own report and the coordinator's
verification are recorded on the pull request.

Copy link
Copy Markdown
Owner Author

@codex review

Head is 626be2e07bb963711de597930489742bb898adbf. A lane closed the live review findings; the coordinator ran the landing gates on this tree -- byte-order-mark, encoding and parse checks on every changed file, a refusal on any tree that deletes more than it adds, ruff check and ruff format --check on the changed Python, the derived documentation pair regenerated to a fixed point, and the documentation gate green.

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown
Owner Author

The soak found a real endurance defect: a faulted engine never comes back

Measured at the pushed head 626be2e07b, on Ubuntu 22.04 in a clean native
worktree. The refusal now names its subject, and the first run with the named
messages produced this:

engine pid 669 faulted at elapsed 185.0s (scheduled 185.0s)
did not recover within the reviewed ceiling (60.0s):
faulted process exited and no replacement ever appeared

This is the first finding on this branch that is about the product rather than
about the soak's own machinery.
Every barrier before it — the missing theme
pack, the dead safety pattern, the incomplete alarms document, the bridge
topology, the unclassified resource tracker — was scaffolding around the
measurement. This is the measurement working.

What was measured

The soak killed the engine on schedule at 185 seconds. It exited. Nothing
started another one, and sixty seconds later the run gave up.

In the same run, the bridge was faulted and did come back:

ZMQ bridge replacement committed; reason=data-flow generation=1 restart_count=2 pid=1855

So replacement exists for one role and either does not exist, or did not fire,
for the other.

What it costs

The objective is a week of continuous thermal-conductivity running without data
loss. That means the stack survives what kills a process: a crash, a kill, an
out-of-memory event. If the engine cannot come back, the run ends at the first
one, and every measurement after it is lost.

This is the roadmap's own crash/restart/election evidence item under the
pre-lab milestone, failing on the target platform, with the evidence in hand
rather than argued.

Also in the log, and possibly the same story

engine incarnation lacks exact shutdown settlement; launcher remains in permanent HOLD

If a killed engine puts the launcher into a permanent hold by design, then the
design and the week-long objective disagree — and that disagreement, not the
hold, is the finding. It will not be resolved by weakening the hold.

What a worker is doing

Establishing, with lines, whether an engine replacement path exists at all;
whether it exists and did not fire; or whether it exists and deliberately
refuses. Those are three different answers and only the middle one is a small
fix.

It is explicitly told to STOP and report rather than improvise if this turns out
to be a missing capability. Restoring a role the system already claims to own is
not a new feature; building an election protocol would be, and that is not
something to invent inside a soak-readiness branch.

The sixty-second ceiling will not be raised to make this pass.

Copy link
Copy Markdown
Owner Author

The engine restart path exists, and a deliberate permanent HOLD blocks it

The worker did not fix this, and was right not to. Its finding, with lines:

engine replacement path, and the owned-engine permanent-HOLD gate that blocks
it, byte-identical at origin/master (launcher.py:5547-5554, 5562-5568,
5651, 5840-5841). Master soak already had the short-profile engine fault
at 185s and the same SIGTERM recovery loop.

Measured, not inferred: _handle_engine_exit is 239 lines at master and 239 at
this head and identical; _check_engine_health 113/113 identical; _start_engine
443/443 identical. This branch did not cause it and does not contain it.

And its own conclusion:

The launcher's owned-engine restart-and-election path remains a master-level
open design item (ROADMAP.md:252 "crash/restart/election evidence"); the
permanent HOLD is deliberate and must not be weakened. Any fix belongs in a
launcher change on master, not this PR.

What this means for the objective, stated plainly

The short profile faults the engine on schedule at 185 seconds and requires
recovery inside a 60-second ceiling. The launcher, by design, holds instead of
restarting.

So a sealed short-profile PASS is not reachable until the launcher can restart
a faulted engine.
That is not a defect in the soak and not a defect in this
pull request. It is a disagreement between a deliberate design decision and the
stated objective of a week of continuous running.

The bridge, faulted in the same run, came back:
ZMQ bridge replacement committed; reason=data-flow generation=1 restart_count=2.
The mechanism exists in the system. It is not wired for the engine.

The three ways out, and all three are the owner's

  1. The launcher gains an engine restart path. This is ROADMAP.md:252,
    already a planned milestone item, and it restores a role the system already
    owns rather than adding a capability it never claimed.
  2. The soak stops faulting the engine. That weakens the evidence, and it is
    not proposed here.
  3. The week runs as-is, with it written down that an engine crash ends the
    run and everything after it is lost.

Option 3 is the honest description of today. It should be known before anyone
travels to the stand, which is why it is recorded here rather than left in a
worker's report.

What the soak has now proven it can do

Nine barriers cleared, each named by evidence and each fix verified by reverting
production. At the current head the run starts, passes its exact-six integration
gate, brings up all four roles, writes 38 samples and 4 runtime closures,
injects a scheduled fault, and recovers the bridge from it.

The instrument works. What it found is a real endurance limit.

# Conflicts:
#	docs/CLAIM_CORRECTIONS.md
#	docs/architecture-montana-important.svg
#	docs/current_candidate_metrics.md
#	governance/agent_preventions_baseline.json
#	tests/docs/test_docs_freshness.py

Copy link
Copy Markdown
Owner Author

@codex review

Head is f00cde45e2bcc6a2f06af521cdbcdf8f60164ff2. A lane closed the live review findings; the coordinator ran the landing gates on this tree -- byte-order-mark, encoding and parse checks on every changed file, a refusal on any tree that deletes more than it adds, ruff check and ruff format --check on the changed Python, the derived documentation pair regenerated to a fixed point, and the documentation gate green.

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown
Owner Author

@codex review

Head under review: f00cde45e2bcc6a2f06af521cdbcdf8f60164ff2

Full check set green with zero pending, and gate.sh measures zero findings at
this head. Requesting the verdict that binds to this exact commit.

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown
Owner Author

Root cause of the short soak's FAIL, measured

The short soak ends with:

"reason": "source stack did not reach the exact four-role startup cut",
"error_type": "_RunnerFoundationError", "state": "FAIL"

log-launcher.txt in the same evidence directory says why the stack never
started, and it is not a launcher fault:

cryodaq.gui._theme_loader.ThemePackError: theme pack 'warm_stone' is unavailable
RuntimeError: Default theme pack invalid: theme pack 'warm_stone' is unavailable

The launcher dies at IMPORT time. cryodaq/gui/theme.py calls resolve_theme()
at module level, and resolve_theme() raises when the DEFAULT pack file cannot
be found.

Why the pack is missing

It is not missing from the snapshot. config/themes/warm_stone.yaml is tracked
(12 packs are), so git archive carries it into the sealed tree.

It is missing because of where the child is told to look.
_source_environment sets

"CRYODAQ_ROOT": str(resolved),

where resolved is the ISOLATED source root — the directory the same function
creates home, tmp, cache and xdg-config inside, and which its own guard
requires NOT to be under the repository. CRYODAQ_ROOT is the highest-priority
input to get_project_root(), and get_config_dir() is get_project_root() / "config". So the child looks for config/themes/warm_stone.yaml under a
directory that was never given a config/ at all.

Measured, not read

On Ubuntu 22.04.5, in a native worktree at master db55845132:

  • A. CRYODAQ_ROOT set to an empty directory, importing cryodaq.gui.theme:
    exit 1, and the traceback is character-for-character the one in the soak's
    log-launcher.txt.
  • B. CRYODAQ_ROOT set to the application tree and CRYODAQ_STATE_ROOT set
    to the isolated directory: exit 0, theme warm_stone, config resolves inside
    the application tree, data resolves under the isolated root.

Tool: evidence/tools/rootprobe.sh in the workspace layer.

What this suggests the fix is

Not copying config/ into the isolated root. paths.py already documents the
seam for exactly this case:

CRYODAQ_STATE_ROOT may independently relocate writable data/ and
logs/ state without changing the read-only configuration and TSP roots.
This keeps sealed/exported application trees immutable during verification.

So the two roots carry different things and the child currently sets only one of
them, for the wrong half:

  • CRYODAQ_ROOT should be the sealed snapshot — read-only configuration,
    which is what the application needs to start.
  • CRYODAQ_STATE_ROOT should be the isolated root — writable data/ and
    logs/, which is what the isolation is actually protecting.

Measurement B is that arrangement, and it starts. The snapshot lives under
/tmp, so the existing guard that the isolated root is not inside the
repository is unaffected.

One thing deliberately NOT proposed here

resolve_theme() refuses to start the program when a colour file is missing.
That refusal is pinned by tests/gui/test_theme_loader.py::test_missing_default_pack_raises,
so it is deliberate, and there is a defensible reading of it: a tree with no
config/ has no safety configuration either, and the theme is merely the first
thing that notices. Changing it is a behaviour decision and is going to the
owner's queue with this measurement attached, not changed here.

What is worth fixing regardless is the message. An operator who reads
Default theme pack invalid will go looking for a theme problem, when the
condition is that the configuration directory is not where the program was told
to look. The message should name the resolved path and the root that produced it.

Copy link
Copy Markdown
Owner Author

The endurance run on the laboratory machine: one barrier removed, a second one found at master

All of this is measured on the laboratory WSL image, Ubuntu 22.04.5, in worktrees cut from the native Linux clone.

The launcher barrier is behind us

The theme barrier reported here on 2026年08月19日 is fixed by #86, and fixing it took two steps rather than one, because the first step moved the failure earlier instead of removing it:

head phase reason evidence written
2dba7043b4 (first attempt) runner passive source fixture topology is not exact summary.json only
93dd5259f4 (with the seal extended) runner reaches the exact-six stage manifest.json, summary.json

Adding a directory to the isolated configuration broke the fixture seal, which walks an exact topology: a fixed set of files plus one empty directory. That is the seal doing its job, so the seal is what had to learn about the theme directory. The packs are now sealed the way the files are — identity, no links, exact mode 0o600, content hashed, and the directory re-listed after sealing so a file appearing mid-seal is a refusal rather than an unsealed byte.

The second barrier is at MASTER, not on the branch

Running the same short soak at db5584513291f04d11bd4db3424f2ebbadbe15a3 — master, without any of #86 — fails identically:

phase runner
reason exact-six collection execution failed: exit code 4
 ImportError while importing tests/integration/test_periodic_png_multiprocess.py
 src/cryodaq/storage/_sqlite.py:31: import sqlite3 as _stdlib_sqlite3
 E ImportError: /lib/x86_64-linux-gnu/libstdc++.so.6: version `CXXABI_1.3.15' not found
 (required by <conda-prefix>/lib/python3.14/lib-dynload/../.././libicui18n.so.78)

So it is not this branch, and it matters more than the barrier above: the storage layer cannot be imported inside the sealed exact-six child on the target machine. The endurance run stops before the program starts.

What it is NOT — five hypotheses eliminated by measurement

sqlite3 imports successfully, version 3.53.2, in every one of these:

  1. the laboratory conda interpreter, in place;
  2. the probe virtual environment's interpreter;
  3. that interpreter with a scrubbed environment (env -i PATH=/usr/bin:/bin);
  4. that interpreter copied to another directory — which is what the sealed stage does — with and without the original prefix on the loader path;
  5. that interpreter copied and run under the sealed stage's exact environment dictionary (HOME=/nonexistent, PATH=/usr/bin:/bin, PYTHONNOUSERSITE, PYTEST_DISABLE_PLUGIN_AUTOLOAD, and the rest).

Import order was also eliminated: PySide6.QtCore, matplotlib, numpy and zmq each imported before sqlite3, in separate processes, all clean.

Tools, so this is repeatable rather than a story: evidence/tools/interpreter_copy_probe.sh and evidence/tools/import_order_probe.sh in the workspace repository.

What is left

The remaining difference between the passing probes and the failing child is the sealed snapshot itself: the child runs inside the extracted archive, with PYTHONPATH set to the snapshot's src, the snapshot root, and the probe environment's site-packages, and with every file in the tree set read-only before collection. The next measurement is to reproduce the child's sys.path exactly, since that is the only variable still standing.

One more fact worth pinning: an earlier run of this soak at fa52b35804 recorded the exact-six gate PASSING (exit_code 0, status PASS) on this same machine. So either the repository or the machine's environment changed under it. That makes this a regression with two candidate owners, and finding which is the next question rather than an assumption.

Copy link
Copy Markdown
Owner Author

This draft is the roadmap item for the laboratory week, and its Ubuntu failure is a collision with #87

Measured 2026年08月20日, so that whoever picks this up does not re-derive it.

Why it matters more than its draft status suggests. The soak's 12h and 72h profiles are the only ones that declare leak criteria — 4 MiB/hour and 1 descriptor/hour after a ten-minute warm-up. The short profile sets both to None and therefore judges no leak at all. I merged #87, #86, #89 and #91 locally on the laboratory machine, cut a clean probe worktree at the result and ran the 12h profile on Ubuntu 22.04 with Python 3.14.6. It refused in under a second:

soak profile '12h' is defined but not activated: the POSIX source-mode runner and
evidence contract are validated only for the short profile; long-duration evidence
remains open

So the week-long guarantee is not blocked by a leak we have not found. It is blocked by having no instrument that can look for one, and this pull request is that instrument.

What is already done, and does not need doing again. The judging half is on master and is profile-generic: every slope check reads the profile's own limit behind an is not None, the series validator checks coverage against duration_s whatever it is, and tests/scripts/test_soak_long_profile_contract.py already exercises a long profile against synthetic samples. What is missing is the entry-point refusal and a real long run.

The Ubuntu failure, opened rather than read from its name

Both failing test (ubuntu-latest, remaining) nodes die in the same place:

scripts/soak_mock_stack_runner.py:582: in _controlled_test_environment
 "LD_LIBRARY_PATH": _controlled_runtime_library_path(),
scripts/soak_mock_stack_runner.py:573: in _controlled_runtime_library_path
 return str(_runtime_library_root())
E scripts.soak_mock_stack_runner._RunnerActivationDisabled

This branch carries its own LD_LIBRARY_PATH implementation, and it refuses unless the library root is a direct directory, is not a symlink, resolves to itself, and has ownership it judges safe. On the hosted runner's conda prefix it refuses.

#87 solves the same problem the opposite way. It resolves the directory, sets LD_LIBRARY_PATH when the directory exists, and simply does not when it does not. That version demonstrably works — the soak reached all four roles with it in the tree, which is the only reason the 12h refusal above could be reached at all.

The owner's standing rule decides between them, and it is not close:

THIS SOFTWARE NEVER REFUSES — a refusal makes the laboratory wire hardware outside CryoDAQ. Keep the check, guide instead, record the receipt.

So this branch should adopt #87's approach in that area rather than the reverse, and the two will conflict there. Worth knowing before the rebase rather than during it.

The Windows failure is NOT the same thing, and I am not claiming it is

test (windows-latest, remaining) prints only:

FAILED NODE: unavailable; inspect preserved stdout.bin and stderr.bin in the candidate artifact.

and the candidate artifact carries no FAILURES section at all — only candidate-suite=remaining failures=[(1, 1)]. LD_LIBRARY_PATH is POSIX, so it cannot be the same cause. That failure still needs opening.

Present state, measured

behind master 60 commits
checks 22 SUCCESS, 7 FAILURE
mergeability CONFLICTING / DIRTY
draft yes
size 4545 additions, 440 deletions, 23 files

The CI hang that was failing every Linux job for the whole of 2026年08月19日 is fixed and merged as #92, so a rebase onto current master starts from a working gate. That was not true when this branch last ran.

Copy link
Copy Markdown
Owner Author

Follow-up, because the strict check is not wrong everywhere — only where the gate runs

I said the strict _runtime_library_root() check refuses on the hosted runner's conda prefix. That stands. The obvious next question is whether it would also refuse on the machine this pull request exists to serve, so I measured that rather than assuming either way (evidence/tools/library_root_probe.py, which reports each condition separately so the answer is a reason and not a verdict).

On the laboratory machine — Ubuntu 22.04.5, the miniforge environment the soak worktrees actually use:

interpreter: /home/cryodaq/miniforge3/envs/cryodaq-lab/bin/python (3.14.6)
library root: /home/cryodaq/miniforge3/envs/cryodaq-lab/lib
 uid=1000 gid=1000 mode=0755, resolves to itself
 ok is a directory
 ok is NOT a symlink
 ok resolves to itself
 ok owner is root or this user
 ok not group- or world-writable

Every condition passes there. So the design is not unusable in the laboratory — it is unusable on the hosted runner, which is where a required check runs, and that alone blocks this pull request.

That narrows the repair rather than widening it. It is not a case of the check being wrong about what a safe library root is; it is a case of a refusal being the wrong response to a root it does not recognise. The owner's standing rule says exactly that:

keep the check, guide instead, record the receipt

which is the shape #87 already has: resolve the directory, set LD_LIBRARY_PATH when it exists, do not when it does not, and never raise. Keeping the strict conditions as a recorded observation rather than an activation refusal would preserve everything this branch was trying to protect and still run on both machines.

One thing I have not measured and am not guessing at: why the runner's prefix fails the check. The traceback proves it does; which of the five conditions it trips would need the probe run inside a job, and that costs a round.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@chatgpt-codex-connector chatgpt-codex-connector[bot] chatgpt-codex-connector[bot] left review comments

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /