Investigate why crc census and score load the target loader in process #1390
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#1390
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
A measurement tool loads the code of its target project into its own process. Loaders pile up across runs, and the loads force every os-sdlc bin test into a fork.
Observed
os-sdlc crc census and score load the target project's Zeitwerk loader file into the caller's process (ExpectedConstants#loaded). Loaders pile up across in-process runs.
Tests isolate it by forking Boot.run (tests/support/in_process_bin.rb).
Every os-sdlc bin test now runs in a fork, so the #1327 journey tracer cannot see os-sdlc bin calls.
Reproduce
n/a: this is a design question, not a failure with a command.
Approach
Answer these questions and state a decision. Why does a measurement tool load the target's code into its own process? Can it run in a child process, or read the constants without loading? Record the journey tracer gap for #1327.
Origin
Occurrence: Stop crc census and score from loading the target Zeitwerk loader into the caller
Seen: plugins/os-sdlc/tests/support/in_process_bin.rb; os-sdlc crc census and score
Run: Session 6c6b6eae-4ebd-4ac4-9b32-535875a6b39b, fast-batch-15 ruling R1
Adds: fast-batch-15 ruling R1 (predicted, 62%) keeps the forked InProcessBin for now and depends on this ticket. Each forked test hides the os-sdlc bin calls from the #1327 use_case/journey tracer. A fix here lets InProcessBin drop the fork. Evidence: docs/crc/os-sdlc/fast-batch-15/rulings.tsv row R1.
Findings (2026-10-27, takedown track; read-only investigation, not built).
Mechanism:
plugins/os-sdlc/lib/os_sdlc/expected_constants.rb:46(loaded) runsload loader_pathon the target's loader file, then :47 collects everyZeitwerk::LoaderfromObjectSpacewhose dirs sit underlib_root. Callers: CRC censuscrc/census/settings.rb:47, scorecrc/score/run.rb:26, and the detectorsdetector/{cycle_edges,error_lineage,holders}/signals.rb(same leak, not named in this ticket). Each run leaves a new loader registered inZeitwerk::Registry, autoload hooks onObjectand the target namespaces, and whatever the loader file requires; a second run re-executes the file andObjectSpacereturns the stale loaders too.Why it loads: it needs
all_expected_cpaths(path -> constant) anddirs(namespaces: true)(root constant). Only the loader config (inflections, collapse, ignore, push_dir) produces those faithfully; a Prism parse would be a heuristic. It never callseager_load.Smallest fix (proposed): in
ExpectedConstants#loaded, read cpaths and the root constant, thenunloadandunregistereach loader it created (unregisteris@experimentalin zeitwerk 2.8.3, loader.rb:365). Alternative B: run the loader file in a child and emit JSON (isolates fully, keeps a fork per caller). Open risk: when the target is os-sdlc itself,OsSdlc.loadermay already be set.Proposed tickets:
A. "ExpectedConstants leaves no loader behind" — 1. After
ExpectedConstants.of, theZeitwerk::Registryloader count is unchanged. 2. Two runs in one process return the same cpaths. 3. The root constant is unchanged. (Fixture project test, red before the fix.)B. "InProcessBin runs CRC bins without a fork" (after A) — 1.
InProcessBin#forked(in_process_bin.rb:58) is gone. 2.tests/crc/*_bin_test.rbpass. 3. Suite time is not worse.Filed from this ticket: #1420 (ExpectedConstants unregisters the loaders it creates) and #1421 (InProcessBin runs CRC bins without a fork; blocked by #1420).
Resolution
Done: Investigated (comment 2026-10-27): ExpectedConstants loads the target loader because only the loader config yields cpaths and the root constant faithfully. Fixed by #1420 (dispose the loaders it creates) and #1421 (InProcessBin no longer forks).
Evidence: #1420 closed, main
9e1f144f; #1421 closed, mainbb9f1f88. With no fork, the #1327 journey tracer can see os-sdlc bin calls in tests.Follow-ups: none