fix(seedbox): pin uint8-util so installing torlink stops crashing the daemon - #150
Merged
Conversation
…queue
Sends reported success and the torrent never appeared — on a freshly
reinstalled, healthy daemon, over both HTTP and SSH. Not an upstream
regression: the fork has only ever published 1.4.3 (Jul 17) and 1.4.4
(Jul 19), so nothing shipped recently. The cause is in torlink 1.4.4's
queue, and our own concurrency cap is what made it fatal.
From the 1.4.4 dist:
add(t,r){
let o=this.items.get(t.id);
if(o&&o.status!=="failed") return; // silent no-op, /add still 200s
s = this.maxDownloads===0 || this.activeCount<this.maxDownloads;
n.status = s ? "downloading" : "queued";
s && (this.startEngine(n), this.ensurePoll()); // engine only starts under the cap
}
Items restored from queue.json at boot keep their "downloading" status but
never get an engine re-attached, so they make no progress and can never
complete or error — meaning promote() never runs and the slot never frees.
With the cap at 2, two such records parked every subsequent add as "queued"
forever. And because add() early-returns on any existing non-"failed" record
while /add still answers 200, retrying produced the same silent success.
It survived reinstalls because nothing ever cleared queue.json.
Two changes:
- Set TORLINK_MAX_DOWNLOADS=0 in all three places the daemons get their
environment. With no cap, add() always starts the engine, so a stale record
can no longer block new torrents. Restore the cap once torlink re-attaches
restored items to a client.
- Move unrestartable queue records aside during provisioning, after
stop_torlink has killed every daemon (so nothing races torlink's persist())
and before the daemons start. Backed up to queue.json.stale rather than
deleted. This is what makes "re-run Setup → Install torlink" actually able
to recover a wedged box instead of reloading the same dead state.
Also rewrite ADD_DROPPED_MESSAGE, which told the user to restart the daemon —
a restart reloads the very queue.json that causes this, so that advice could
never work. It now points at the installer and says retrying will not help.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
ralyodio
marked this pull request as ready for review
July 31, 2026 05:14
The previous commit claimed items restored from queue.json never get their
engine re-attached. That is wrong: restore() does call startEngine() for
items saved as "downloading", in both the 1.4.3 fork base and upstream 1.5.1.
The real mechanism, verified in src/download/queue.ts:
- activeCount counts items whose status is "downloading".
- promote() starts a queued item only while activeCount < maxDownloads.
- A stalled download (dead magnet, no peers) holds "downloading" forever —
it never completes and never errors — so its slot never frees.
So at a cap of 2, two dead torrents park every later add as "queued"
indefinitely. add() then returns early for any existing non-"failed" record
while /add still answers 200, making a re-send a guaranteed silent no-op.
Comments only — the fix itself (cap off, clear stale queue records on
provision, honest failure message) is unchanged and still correct.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… daemon
Replaces this branch's earlier theory, which was wrong twice over. There
were never stale queue records — queue.json on the affected box is 2 bytes
and /status reported downloads: [] — and TORLINK_MAX_DOWNLOADS was never
blocking anything. Both of those changes are reverted here.
The actual cause, from the daemon's own journal:
06:53:28 POST /add -> 200
06:53:56 TypeError: ... Received undefined
at arr2hex (uint8-util/dist/src/node.js:12)
at Torrent._processParsedTorrent (webtorrent/lib/torrent.js:330)
06:53:56 Main process exited, code=exited, status=1/FAILURE
06:54:01 Scheduled restart job, restart counter is at 4
uint8-util 2.3.x restructured the package and broke arr2hex. It reaches us
transitively through webtorrent on a caret range, so `npm i -g` silently
resolves to it. torlink then accepts the magnet, starts fetching metadata,
and dies on an uncaught TypeError — taking the in-memory queue with it. The
app sees 200 from /add and never finds the torrent in /status. The box had
crashed 28 times, once per send.
Reinstalling never helped because the reinstall is what re-fetched the
broken version, which is also why the GUI installer and a manual `npm i -g`
behaved identically. Upstream already fixed this by pinning 2.2.6
(baairon/torlink 5293408); this applies the same pin after the global
install so the installer stops re-introducing the crash. Drop it once the
fork ships a release carrying the pin.
Also rewrites ADD_DROPPED_MESSAGE, which blamed a "wedged" daemon and told
the user to restart it. The daemon is crashing, not wedged, and a restart
alone changes nothing.
Verified: pinned 2.2.6 on the live box and the add path now works end to
end — POST /add 200, metadata parsed, torrent present in /status, daemon
still up 45s later, crash count unchanged. The generated step was then run
against that box and correctly reported "already pinned", confirming it is
idempotent. 140 tests pass; `bash -n` clean on the generated script.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sends returned success and the torrent never appeared. Root-caused on the live box.
Earlier revisions blamed stale
queue.jsonrecords and theTORLINK_MAX_DOWNLOADScap. Both were wrong — on the affected boxqueue.jsonis 2 bytes and/statusreporteddownloads: [], so there were no stale records and the cap was never blocking anything. Those changes are reverted in this PR.The actual cause
From the daemon's own journal:
uint8-util2.3.x restructured the package and brokearr2hex. It reaches us transitively through webtorrent on a caret range, sonpm i -gsilently resolves to it.torlink accepts the magnet, starts fetching metadata, then dies on an uncaught TypeError — taking the in-memory queue with it. systemd restarts it and the torrent is simply gone. The app sees
200from/addand never finds it in/status. The box had crashed 28 times, once per send.This explains every symptom: 200 from
/add, both transports identical, nothing in/status, emptyqueue.json, worked-days-ago, and reinstalls never helping — each reinstall re-fetched the broken version. It's also why the GUI installer and a manualnpm i -gbehave identically: they run the same command.Upstream already fixed it —
baairon/torlink@5293408 fix(boot): pin uint8-util 2.2.6 and self-heal boot after engine crashes.Changes
uint8-util@2.2.6into the global package right afternpm i -g, matching upstream's fix, so the installer stops re-introducing the crash. Placed afterPKG_ROOTis assigned — the script runs underset -u, so referencing it earlier would abort the provision. Idempotent: re-pinning emitsskip. Remove once the fork ships a release carrying the pin.ADD_DROPPED_MESSAGE, which blamed a "wedged" daemon and told the user to restart it. The daemon is crashing, not wedged, and a restart alone changes nothing.Verification
Done against the live seedbox, not just in tests:
POST /add→ 200, metadata parsed (name resolved), torrent present in/status, daemon stillactive45s later, crash count unchanged. Test torrent removed, no files left behind.STEP|uint8|skip|uint8-util already pinned at 2.2.6— confirming it's idempotent.bash -nclean on the generated provisioning script.Related
ralyodio/torlink#1syncs the fork to upstream 1.5.1, which carries this pin properly. Once that's merged and published, this workaround can be dropped.Independent of #149, though both touch
provision.ts.