← board

The managed-ownership campaign is producing one regression per landing

Measured 2026-09-01 by frankZ at db706c2da, binary 59699dc0833f8110, from Track T's bisect windows on seven plus local reproduction of each.

The table is the whole ticket

red the ONE buildable commit in its bisect window that commit's subject
test-threads#test_threadsafe_refcount_lockfree (fail=3, 5/5) b788c5865 one helper for the string-to-pointer ownership seam, and the three that were still leaking
test-core#test_rtl_fpc_compat_helpers (SIGSEGV) 65e15e5ab give a dynamic array reaching a pointer destination an owner too
test-core#test_interface_byval_param_no_leak (24/25) 1308ef1f8 release an interface function result, and flush a bare loop body
test-threads#test_exception_threads_race (SIGSEGV, 3/3) 620989250 every caught exception object leaked on every backend, both Pascal shapes

A fifth landing in the same campaign, 4af4645ba "a discarded managed function result had no owner, so it just leaked", sits among them.

All five carry the same Claude-Session id. One session, one evening, one seam, five fixes, four new reds — and each red's window was narrowed by Track T independently of this reading.

Each failure lands squarely on its own commit's subject, which is what makes the attribution more than a coincidence of ordering:

Why this is a ticket and not four

b788c5865's own header already says it: "SIXTH, SEVENTH AND EIGHTH INSTANCE OF ONE SHAPE... Three was already a design flaw; eight settles what the common cause is." It is right about the ORIGINAL defect. What it does not say — and could not, from inside one commit — is that the generalisation is landing at a rate of about one new red per fix.

That is a rate nobody in the campaign can see. Each session sees its own commit go green against its own repro; the next tier sweep attributes the red to whoever reads it next. It took a lane holding the whole red set at once for the four to line up, which is the argument for having one.

Nothing here says the campaign is wrong or should stop. Eight instances of one shape is exactly the thing worth generalising, and devdocs/dev/root-cause-over-microfix.md says the overhaul is usually the smaller job. The claim is narrower and it is about VERIFICATION: a change to where ownership is decided reaches every managed type, and the per-fix gate (make compiler/pascal26 plus one repro) cannot see it, because compiler.pas exercises almost none of the managed surface. Five landings, five green gates, four reds.

The cheap thing that would have caught all four

The four failing programs cost well under a minute between them and none needs the full tier:

./compiler/pascal26 -Fulib/rtl test/test_rtl_fpc_compat_helpers.pas        /tmp/a && /tmp/a
./compiler/pascal26 --threadsafe test/test_threadsafe_refcount_lockfree.pas /tmp/b && /tmp/b
./compiler/pascal26 test/test_interface_byval_param_no_leak.pas            /tmp/c && /tmp/c
./compiler/pascal26 --threadsafe test/test_exception_threads_race.pas      /tmp/d && /tmp/d

-Fulib/rtl is load-bearing on the first — without it the crash does not happen — which is its own small lesson about carrying the recipe's flags into a repro.

Whether that belongs in gate.sh quick or as a habit for this campaign is a judgement for whoever owns the seam, not for me.

Not claimed

Filed, not fixed. The seam is under active generalisation by one session and a second pair of hands in it would be the tenth instance of the shape rather than a fix. Blocks [[umbrella-one-full-tier-run-with-no-red-tier]].

Resolved — all four green

All four reproduced at HEAD exactly as filed, and all four are green at d5e0a1e48:

program before after
test_rtl_fpc_compat_helpers SIGSEGV 23 / 23
test_threadsafe_refcount_lockfree fail=3 fail=0
test_interface_byval_param_no_leak 24 / 25 25 / 25
test_exception_threads_race SIGSEGV rc=0

Two causes, not four. Three of them were one: the park fired on already-owned values, whose release lands at the enclosing scope's exit. The fourth was the interface function-result temp sitting on the end-of-statement queue when FPC holds it to scope exit.

The verification claim is the part worth keeping, and it was right. An ownership change reaches every managed type, and make compiler/pascal26 plus one repro cannot see it — compiler.pas exercises almost none of the managed surface. Five landings, five green gates, four reds. CLAUDE.md already says the fixedpoint cannot see a construct the compiler never writes; the commits that caused this quoted that rule and still did not run four programs costing under a minute between them. -Fulib/rtl being load-bearing on the first is exactly the kind of thing that makes a repro set worth writing down rather than reconstructing.

Fixed in commit d5e0a1e48.

Log

The habit, not the guard — carry this to the next campaign

frankZ's framing, and it is the right one: a row in gate.sh quick guards against exactly these four programs, which are now fixed. A SET of four is a habit someone can carry to the next ownership campaign, which is where the value is. So it lives here.

When a change touches managed ownership, run these four before pushing. They cost under a minute between them and each covers a managed kind the self-host fixedpoint cannot reach:

./compiler/pascal26 -Fulib/rtl test/test_rtl_fpc_compat_helpers.pas        /tmp/a && /tmp/a
./compiler/pascal26 --threadsafe test/test_threadsafe_refcount_lockfree.pas /tmp/b && /tmp/b
./compiler/pascal26 test/test_interface_byval_param_no_leak.pas            /tmp/c && /tmp/c
./compiler/pascal26 --threadsafe test/test_exception_threads_race.pas      /tmp/d && /tmp/d

-Fulib/rtl is load-bearing on the first — without it the crash does not happen at all, so a repro that drops the flag exits 0 and certifies nothing. That is a guard that cannot fail, in the exact sense CLAUDE.md means it.

frankA's sharpening of the diagnosis is worth keeping too, because it moves the fault off "we were not careful enough": the fixedpoint answers a question about compiler.pas and gets READ as answering one about the compiler. It is not a weak gate, it is a gate that cannot express this property. The remedy is therefore a habit that carries a repro in a shape compiler.pas never emits — one per change — not more caution about the same gate.