Found while removing the pg native binding (PR #10677): there were two native pg implementations, both
defining the same extern "C" symbols, and link order silently decided which one was live.
Nothing reported this. There is no gate that notices two crates exporting one extern "C" surface; the linker
just picks one. Whichever it picked is what users got, and a fix applied to the other one would have looked
inert — the same failure shape as the stale-archive trap CLAUDE.md documents, but caused by duplication rather
than staleness.
This is probably not unique to pg
mysql2 shows the same two-location shape: crates/perry-stdlib/src/mysql2/ (~1870 lines) alongside a
crates/perry-ext-mysql2 crate. That one is being untangled now as part of its own removal, but the pattern
suggests the #466 migration (moving bindings from perry-stdlib into perry-ext-* crates) left duplicates behind
in more than one place.
There are currently ~38 crates/perry-ext-* crates. Any of them whose package also has a crates/perry-stdlib/src/<pkg>/
directory is a candidate.
Suggested work
- Enumerate it. For each
perry-ext-* crate, check whether a same-named perry-stdlib/src/ module exists, and
whether the two export overlapping extern "C" symbols.
- Add a gate. A duplicate
#[no_mangle]/extern "C" symbol across crates should be a build or lint failure,
not a link-order coin flip. This repo already ratchets several similar properties (addr-class, raw-handle,
unrooted-local-shape), so the machinery exists.
- Delete the dead side of each pair. Where both are live-ish, decide which is canonical before the removal
campaign reaches that package — otherwise a removal PR deletes one and leaves the other quietly serving traffic.
Why it matters for the current work
The owner's goal is to delete these hand-written Rust reimplementations in favour of compiling the real npm
packages. A removal that deletes only the registered copy would leave a shadow implementation in the tree that
still claims the symbols — the package would appear to be "removed" while a duplicate continued to answer. #10677
avoided that only because its agent went looking.
Found while removing the
pgnative binding (PR #10677): there were two nativepgimplementations, bothdefining the same
extern "C"symbols, and link order silently decided which one was live.crates/perry-ext-pg/— the registered binding (sqlx::postgres + tokio via perry-ffi).crates/perry-stdlib/src/pg/— an older duplicate that survived the Native package ecosystem v1: open, extensible, with zero developer-facing change #466 migration behind abundled-pgfeature, exporting the same symbols.
Nothing reported this. There is no gate that notices two crates exporting one
extern "C"surface; the linkerjust picks one. Whichever it picked is what users got, and a fix applied to the other one would have looked
inert — the same failure shape as the stale-archive trap CLAUDE.md documents, but caused by duplication rather
than staleness.
This is probably not unique to pg
mysql2shows the same two-location shape:crates/perry-stdlib/src/mysql2/(~1870 lines) alongside acrates/perry-ext-mysql2crate. That one is being untangled now as part of its own removal, but the patternsuggests the #466 migration (moving bindings from
perry-stdlibintoperry-ext-*crates) left duplicates behindin more than one place.
There are currently ~38
crates/perry-ext-*crates. Any of them whose package also has acrates/perry-stdlib/src/<pkg>/directory is a candidate.
Suggested work
perry-ext-*crate, check whether a same-namedperry-stdlib/src/module exists, andwhether the two export overlapping
extern "C"symbols.#[no_mangle]/extern "C"symbol across crates should be a build or lint failure,not a link-order coin flip. This repo already ratchets several similar properties (addr-class, raw-handle,
unrooted-local-shape), so the machinery exists.
campaign reaches that package — otherwise a removal PR deletes one and leaves the other quietly serving traffic.
Why it matters for the current work
The owner's goal is to delete these hand-written Rust reimplementations in favour of compiling the real npm
packages. A removal that deletes only the registered copy would leave a shadow implementation in the tree that
still claims the symbols — the package would appear to be "removed" while a duplicate continued to answer. #10677
avoided that only because its agent went looking.