Commits tab: page 2 (+skip) throws 'Maximum call stack size exceeded' and sticks on 'loading history…' forever #529
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#529
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?
Commits tab: page 2 (+skip) throws "Maximum call stack size exceeded" and sticks on "loading history…" forever
What's requested
Fix the commits-history pager: navigating from page 1 to any "?skip=N" window (
older →) throwsRangeError: Maximum call stack size exceededinside Solid's effect system, surfaces a toast with the data-layer cache key (sha:<40-hex>:commits::35— i.e. keysha:{sha}:commits:{path}:{skip}with empty path, page size 35), and leaves the page permanently on theloading history…fallback. Diagnosis is static (code reading + user evidence: toast chip + message, page-2 hang); nothing was reproduced locally.Evidence
sha:…:commits::35matches the page-2 cache key built inweb/src/pages/Commits.jsx(CommitList, lines ~216-228):`sha:${sha}:commits:${path()}:${skip()}`— emptypath,skip=35(second window of the default n=35 page,internal/api/commits.goline 30). The chip is the tray entry'skey, set byreportError(err, key)inweb/src/lib/data.js(start(), line ~196). So the failing unit is the Commits page-2useDatawindow, not the network call itself.h()(Commits.jsx line ~229) returnsgetPage(), and a faileduseDataentry sets onlyentry.error(data.jsstart()), never a value — the<Show when={h()} fallback={<p>loading history…</p>}>fallback renders indefinitely.internal/api/bind_wal.goCommits()is a single non-recursivegit log --skip=… --max-count=n+1render, ETag'd immutable (ccSWR/ccImmutable per head SHA). No server recursion exists.Recursion mechanism (exact)
The page-2 branch of
CommitListcallsuseData(...)INSIDE acreateEffectbody (Commits.jsx lines 216-228):useData(data.js) itself opens acreateEffectthat readsentry.signal[0]()— a signal write/read cycle entered synchronously from inside a running effect:useDatais invoked during effect execution → its innercreateEffectrunsstartIfStale→start()→ the async fetcher throws synchronously or the fetch rejects.start()'s catch callsreportError(err, key)(the toast) andentry.error = err;entry.signalis never set with a value.setPage(get())in the outer effect then depends on the inner effect's signal; because the innercreateEffectwas created inside an active effect scope, Solid nests its scope under the outer effect. Any re-run of the inner effect (retry, key re-read,setValue(entry.signal[0]())on a hot path) re-triggers the outer effect, which callsuseDataagain, creating a new nested effect each cycle — the effect graph grows without bound and Solid's synchronous dependency flush blows the JS stack:RangeError: Maximum call stack size exceeded.entry.erroris set but no value ever lands in the signal, soh()stays undefined and the page hangs onloading history…even though page 1's cached data (getFirst()) is still in the cache and would render fine.Note
h()'s guard (skip() === 0 && !path() ? getFirst() : getPage()) means the bug only fires beyond the first window — exactly the reported "page 2" trigger.Fix
useDatainside the effect. Hoist it to component scope with a reactive key, the Tree.jsx DocTabs pattern (web/src/pages/Tree.jsx~line 103):useData(() => (pageKey() ? \sha:${sha}:commits:${path}:${skip}` : "commits:none"), () => (pageKey() ? repoClient.commits({...}) : null), SHA_TTL)` — a null/"none" key means "do not fetch", so the first page and later windows share one subscription and no nested effect is ever created.useDatawindow currently renders as an eternalloading history…. Add an error state (inline retry, the DegradedNotice precedent) so a page-2 fetch failure shows a human-readable error instead of an eternal pulse; no rawRangeErrorstrings in the tray for this path.Architecture notes
useDatasupports a reactive GETTER key (documented in data.js) — that is the intended way to change keys inside a component; calling the hook itself inside an effect is the misuse.sha:<sha>:commits:*entries are SHA_TTL=Infinity immutable windows; nothing server-side needs to change.{e.key}in App.jsx tray). Acceptable as a debug affordance, but the acceptance criteria below cover not reaching that path on expected errors.Acceptance criteria
older →window) of the Commits tab loads without a RangeError; no tray entry appears on success.useData/useResolvedcall site lives inside acreateEffectbody (grep sweep ofweb/src/pages/*.jsx— Commits.jsx lines 216-228 is the offender; confirm no siblings share the pattern).loading history….?path=windows still work).Fixed by #541 (branch fix/issue-529): page-2 useData hoisted out of the createEffect into a top-level reactive-key subscription (DocTabs pattern), failed windows render an inline error with retry instead of the eternal loading fallback, page-1 path unchanged. 8/8 new tests green, vite build green; only failure in the full suite is the ambient live-server smoke subtest (/setup 403 on the standing :8080, unrelated).
Review of PR #541 (fix/issue-529), verified in scratch worktree /tmp/pr541 (removed afterward; main worktree untouched, still clean on main).
FINDINGS (all acceptance criteria hold):
TESTS (scratch worktree, node_modules symlinked from main):
No fixes needed; nothing pushed.
MERGE RECOMMENDATION: ready to merge.
Fixed by PR #541 (review clean — all 6 checks pass, repo-wide sweep confirms zero hook-in-effect sites), merged. Closing.