The JS wire push does not build its own pack: shared/wire.js buildPushPack shells to the native keeper upload-pack <store>?/<proj>, driving a full upload-pack negotiation with a spawned keeper just to harvest the packfile. That makes every jab post/jab put over the wire depend on the native keeper binary at runtime (and blocks a push wherever it is absent). The pieces to do it in JS already exist — the git.pack writer bindings, the keeper-store reader (commitParents/readTree/getObject), and jab post's local pack build (fold-commit.writePack). Fix: replace the keeper spawn with a pure-JS thin push-pack — walk the reachability closure (want=tip MINUS have=remote tips) over the JS store and emit via git.pack, no subprocess. Reuse PACK-002's OFS writer for offset-only deltas; keep the packfile fsck-clean on the peer. JS only (be/, via jab). Method: work; sibling GIT-013/GIT-016.
shared/wire.js buildPushPack(localServe, wantSha, haves) (~L393) does io.spawn(KEEPER_BIN, ["keeper","upload-pack", localServe]), drains keeper's advert, sends want/haves/done, and reads the pack after NAK — a native pack build wearing a wire-negotiation costume.verbs/post/post.js pushRemote and verbs/put/put.js pushWire both call buildPushPack before wire.push, so BOTH break without a keeper binary.git.pack package (git.pack.ram/over, feed, header, finish), the store reader (getObject/commitParents/readTree), and jab post fold-commit.writePack (a local commit pack) — PACK-002 tracks giving that writer OFS_DELTA.wire.fetch + ingest); only the push-pack BUILD still delegates to the native binary.buildPushPack assembles the push pack in pure JS: compute the object closure reachable from wantSha and NOT from haves, then emit a valid packfile via git.pack — NO keeper spawn, no subprocess.jab post/jab put over ssh/http completes with no native keeper binary present; the peer accepts the pack (git receive-pack fsck-clean, unpack ok).WALK_INCL_ANCHOR) so the peer does not fsck missing blob.be/, run via jab. Do NOT run native be; do NOT open beagle/.git.pack writer, the store reader closure walk, fold-commit.writePack, PACK-002's OFS_DELTA encoder. No hand-rolled pack byte-twiddling outside git.pack; no new deps.GIT-018 comments, greppable, ticket-coded ≤64-char commit.buildPushPack's signature (localServe, wantSha, haves) so pushRemote/pushWire are untouched; swap only the body (spawn → JS walk+emit).unpack ok + fsck-clean. — test/wire/push-nokeeper (KEEPER_BIN pointed at a non-existent path).e42c94e7 (wire.js markReachable).git.pack + sha1 trailer; the GIT-009 anchor rides the full tree closure (a committed blob, walked naturally). RAW records this pass — OFS_DELTA stays PACK-002 (noted in emitPack). — landed e42c94e7.buildPushPack's body to the JS builder; drop the keeper upload-pack spawn. — landed e42c94e7 (signature kept, spawn gone).push-nokeeper + wire bucket.receive-pack accepts a non-thin pack, so the closure approach is safe; a thin pack needs base bookkeeping against haves — defer unless cheap.e42c94e7 ("dont use the native binary for making packs", be/ trunk): shared/wire.js buildPushPack is pure JS — serveReader + markReachable closure walk + emitPack (git.pack.over, sha1 trailer); no keeper spawn. Test test/wire/push-nokeeper.261680bf serves fetches with the same markReachable/buildPushPack core (shared/serve.js).