Skip to content

fix(local-installation): do not offer a local model other programs leave no memory for - #33

Open
oldwinter53 wants to merge 3 commits into
mainfrom
fix/local-models-occupied-memory
Open

oldwinter53 wants to merge 3 commits into
mainfrom
fix/local-models-occupied-memory

Conversation

@oldwinter53

Copy link
Copy Markdown

Summary

Since the localmodels pin moved to 0.1.0.dev31, setup inspect reports capable hardware as available: true with basis: "capacity" and a free-memory-required reason while other programs hold the memory the model needs. The plan in that report assumes those programs are closed. The installer read only available, offered the local model, and stack plan, which uses the memory free at that moment, then refused. Setup ended on "This computer can't run the local model right now" with no cause anywhere.

  • inspectSetup reports such a computer as not eligible, keeping the reason and the model the hardware could serve.
  • The desktop setup page shows that model, not selectable, with "Other apps are using the memory this model needs. Quit them, then reopen Linger."
  • When the planner refuses anyway, its cause goes into the outcome message and its redacted output into the installer log.

Verification

  • Reproduced with the real dev31 darwin-arm64 kit on a Mac with 2.4 GB available: setup inspect returned available: true with free-memory-required, and the same stack plan the installer runs exited 2 with "no verified LLM fits".
  • With this change, inspectSetup against that kit on the same Mac returns eligible: false, reasons: ["free-memory-required"], model lfm3-1.7b.
  • @linger/local-installation: typecheck, lint, setup-plan.test.ts and local-models.test.ts. @linger/desktop: typecheck, lint, local-server.test.ts (89 passed). @linger/linger-installer: typecheck.

Risks

  • Not confirmed on the Mac mini where the failure was reported; its logs were not reachable. The setup page change was not looked at in a running app.
  • The installer's own web page hides the local model on such a computer without the note; only the desktop setup page shows it.

🤖 Generated with Claude Code

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (2 minor, 1 nit)

Scope: base main, merge-base 36fff89; 1 commit, 8 files reviewed at c338c9c. Loaded coderbook. ponytail-review and karpathy-guidelines were not loaded; their checklists were applied from memory. Ran in a separate session.

Findings

Minor

  1. apps/desktop/src/main/local-server.ts:570: "memory in use" is decided from two different upstream fields.
    inspectSetup (packages/local-installation/src/setup-plan.ts:192) makes a computer ineligible when stack.basis === "capacity". The desktop then sets memoryInUse only when the reason list contains free-memory-required. These are two sources of truth for one fact.

    • Failure scenario: localmodels reports basis: "capacity" with a different or extra reason code, or with no reason. The package marks the computer ineligible, but the desktop sets no memoryInUse and hides the model with no note. That is the same unexplained outcome this PR sets out to fix.
    • Fix: have inspectSetup emit the decision once as a structured field (for example localModels.memoryInUse: value.stack.available && value.stack.basis === "capacity"). The desktop then reads that field instead of matching the reason code again.
  2. apps/desktop/src/shell/setup.html:2130: the new blocked branch has no test.
    The blocked state shows the model row, replaces the needs line with "Other apps are using the memory…", disables the toggle and forces plan.localModel = false. apps/desktop/src/shell/setup-page.test.ts has no local-model case, and the PR says the page was not looked at in a running app.

    • Failure scenario: a later edit to renderLocal lets plan.localModel stay true while the row is blocked. Quick Install would then run the planner that this PR shows fails.
    • Fix: add one setup-page test for { state: "checked", eligible: false, memoryInUse: true, model }. It should assert that the toggle is disabled, the note is shown, and the plan summary leaves out "Local model".

Nit

  1. packages/local-installation/src/local-models.ts:289: run-on sentence. When cause has no closing period, the message reads "…could not create a local model plan: stack: no verified LLM fits this computer Existing resources were preserved." managed-runtimes.ts:81 and model-gateway.ts:153 put the cause last. Either do the same or add the period yourself.

Verified as fine

  • assertLocalModelsEligible now rejects a capacity-basis plan, so the install path and the desktop check agree. setup-plan.test.ts covers this, and the estimate still names the model.
  • The planner failure path reuses the existing localmodelsFailure helper. It redacts output, splits it into log events and drops progress lines, as managed-runtimes.ts does. No new parsing was added.
  • The desktop inspection runs once per app run (local-server.ts:463), so "Quit them, then reopen Linger" really does re-check memory.
  • When blocked, plan.localModel is forced to false and the toggle is disabled, so the default plan (localModel: false) still shows "Quick Install".
  • No other consumer of localModels.eligible in apps/ or packages/ is affected. The installer's own page hides the model without a note, which the PR states as a known gap.
  • No new env vars, contracts, migrations or cross-app imports. All text is English. The commit message follows the conventions.

Process notes

  • The branch is BEHIND main, and main requires up-to-date branches, so it needs a rebase before merge.

CI

At review time, CodeQL, check (computer-go), check (contracts), check (teamhub), Analyze (actions) and changes passed. check (desktop), check (installer), check (packages), check (teamhub-cli), check (web) and repository-gate were still pending. No tests were run locally; the change does not touch apps/computer/.

Automated review by Claude Code


Automated review of c338c9c by Claude Code.

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 minor; 3 previous findings still open: 2 minor, 1 nit)

Scope: follow-up on c338c9c. Base main, merge base 030a1b0; 3 commits (c338c9c, 236054e, merge e75af75), 8 files reviewed, focusing on the new commit 236054e. Helper skills (coderbook, ponytail-review, karpathy-guidelines) were not loaded as separate skills; I reviewed against the review rules directly. Ran in a separate session.

Previous findings

  • Not resolved: Memory-in-use is decided from two different upstream fields. apps/desktop/src/main/local-server.ts:570 still keys memoryInUse on the free-memory-required reason code, while packages/local-installation/src/setup-plan.ts:192 keys eligibility on basis === "capacity".
  • Not resolved: No test for the setup page's disabled branch. apps/desktop/src/shell/setup.html (blocked) is still not covered; setup-page.test.ts is unchanged.
  • Not resolved: Missing sentence separator after the cause. The new refused() helper (packages/local-installation/src/local-models.ts:229) still builds `${text}: ${cause} Existing resources were preserved.`. The new test even asserts "...held by another program Existing resources were preserved.". Because InstallerApi now appends job.message (local-server.ts:2290), this run-on sentence is now shown on the desktop setup page too.

Findings

Minor

  • packages/local-installation/src/local-models.ts:283 (return captured ? result : { ...result, stderr: tail }) and :257-258. In the uncaptured stack apply --jsonl run, every non-progress line goes into tail, including JSON lines, and tail is passed to localmodelsFailure as stderr with an empty stdout. errorEvent() (localmodels-failure.ts:38) only scans stdout, so a structured error line from upstream is never parsed. Upstream uses this shape for other commands: localmodels.gateway-error/v1 and localmodels.harness-error/v1. When stack apply exits on such a line, the error code is lost, and the cause falls back to "the last 3 stderr lines joined". The user then sees raw JSON in the outcome message, which now also appears on the desktop page, e.g. ...did not finish: {"schema":"localmodels.stack-error/v1","code":"...","message":"..."} Existing resources were preserved.. The stdout-only {"kind":"progress" filter in the log also no longer applies to these lines. Fix: return JSON lines from the tail as stdout and plain text lines as stderr, so localmodelsFailure treats an apply failure the same way it treats a captured run. Then add a test where apply emits an error JSON line.

Verified as fine

  • refused() reuses the existing localmodelsFailure helper for both stack plan and stack apply. It does not copy the cause/log logic, and the old inline plan-failure block was removed.
  • The tail buffer is bounded (8000 chars), and per-chunk parsing still forwards byte progress. The redacted output goes only to installer log events; the outcome cause goes through line() redaction and the 300-char cap.
  • capture is unchanged for every command; the only change is that captured is now a named variable.
  • InstallerApi keeps its fixed advice for LOCAL_MODEL_FAILURES and appends the installer message only when one exists; the test covers the appended form.
  • The changes from the first review (eligibility on basis !== "capacity", the memoryInUse field, the disabled row with the note) are unchanged since c338c9c.
  • Commit 236054e follows the conventional-commit format. Both commit authors are linked to GitHub accounts, and the branch is not behind main.

Process notes

  • None that need action.

CI

  • CodeQL, changes, check (computer-go), check (contracts), check (teamhub): pass.
  • check (desktop), check (installer), check (packages), check (teamhub-cli), check (web), repository-gate: still pending when this review ran.
  • I ran no tests locally. The finding follows directly from the code: errorEvent reads only result.stdout, and the apply path returns an empty stdout.

Automated review by Claude Code


Automated review of e75af75 by Claude Code.

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 minor carried over, 0 new)

Scope: follow-up on e75af75, reviewing head f938c95 against base main (merge base 030a1b0). One new commit and 2 files changed since the previous review (apps/desktop/src/main/local-server.ts, apps/desktop/src/main/local-server.test.ts). I read the installer's job manager (apps/linger-installer/src/server/jobs.ts) for context. I did not load the helper skills (coderbook, ponytail-review, karpathy-guidelines), so I checked the items from the review checklist directly. This review ran in a separate session.

Previous findings

  • Minor: a JSON error line from apply is treated as stderr (packages/local-installation/src/local-models.ts:283): Not resolved. packages/ has not changed since e75af75, and return captured ? result : { ...result, stderr: tail }; is still there. When the upstream error line is captured as stderr, errorEvent (which reads stdout) misses it, so the person sees raw JSON. Fix: send JSON lines to stdout and text lines to stderr.

Findings

No new findings in the changes since e75af75.

Verified as fine

  • Root cause of the new fix. JobManager.start (apps/linger-installer/src/server/jobs.ts:270-276) returns any existing job for the instance that is still active. active includes waiting_user (jobs.ts:25-26). The desktop only reuses queued/running jobs (local-server.ts:2204-2213). So before this commit, a waiting_user attempt fell through to POST /api/jobs and was handed back every time, with the same failure. Cancelling it first (local-server.ts:2218-2222) fixes this at the source.
  • What cancel does. For a job that is not executing, cancel marks it canceled and keeps account, data and device (jobs.ts:473-487, apps/linger-installer/AGENTS.md:12). That makes it safe to run before starting a new job, including the fresh path and a stale RUNTIME_AUTH_REQUIRED job whose instance login has gone.
  • Jobs waiting for local-model approval or recovery. A job waiting on LOCAL_MODELS_APPROVAL_REQUIRED or LOCAL_MODELS_RECOVERY_REQUIRED is now cancelled and restarted rather than resumed. The new job goes back through the approval and replan handling in the same loop, so the behaviour stays correct; it only re-runs the early stages.
  • Encoding and errors. The job id is URL-encoded, and a failed cancel throws through #json with the installer's message.
  • Test. starts again past an attempt that stopped waiting for the person asserts the cancel, the new local-models job and the ready phase. Without the fix, canceled would be empty, so the test fails as it should.
  • Docs. apps/desktop/AGENTS.md does not describe the job-reuse rule, so no doc update is needed.
  • Conventions. Comments are in English and explain why. The change imports nothing across apps.

Process notes

  • The branch is BEHIND main, which requires up-to-date branches, so it needs a rebase before merge.

CI

At review time, CodeQL, changes, check (computer-go), check (contracts) and check (teamhub) had passed. check (desktop), check (packages), check (installer), check (web), check (teamhub-cli) and repository-gate were still pending. I ran no tests locally because this worktree has no installed dependencies; the changed desktop test is covered by check (desktop).

Automated review by Claude Code


Automated review of f938c95 by Claude Code.

oldwinter90 and others added 3 commits November 11, 2026 21:09
…ave no memory for

localmodels 0.1.0.dev31 reports capable hardware as available, with a
capacity plan and a free-memory-required reason, while other programs
hold the memory. Installing plans against the memory free then, so the
planner refused and setup ended on a generic failure. The report now
marks such a computer not eligible, the setup page names the model and
says to quit other apps, and a refused plan carries the planner's cause.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The setup page replaced every local model failure with one fixed
sentence, and a stopped deployment carried no cause at all because its
output is not captured. The page now appends the installer's message,
and a refused plan or stopped deployment names the localmodels cause
and logs its redacted output.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The installer answers a new job request with one still waiting for the
person. An attempt that stopped on a local model failure stayed in that
state on disk, so every later Try Again, across restarts and upgrades,
got the same job back and failed at once without running anything.
The desktop now cancels such a job before it asks for a new one.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@oldwinter53
oldwinter53 force-pushed the fix/local-models-occupied-memory branch from f938c95 to cdafb8a Compare November 11, 2026 13:09

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on f938c95. Base main, merge-base 90e380d (current origin/main tip, so the branch is up to date). Reviewed 3 commits and 8 files at cdafb8a. Loaded coderbook, ponytail-review and karpathy-guidelines. Ran in a separate session.

Previous findings

The previous review (f938c95, approve with nits) had no findings, so there is nothing to track.

What changed since f938c95

The branch was rebased onto main and the merge commit e75af75 was dropped. git range-diff shows each of the three commits as patch-identical (=) to its earlier version:

  • 66d7224 fix(local-installation): do not offer a local model other programs leave no memory for
  • fef2c1b fix(local-installation): say why a local model deployment stopped
  • cdafb8a fix(desktop): start a new installation past an attempt left waiting

main gained commits in between, but they only touch apps/node-services (runner-gateway adaptive limits), apps/teamhub (voice live canvas), acceptance scenarios and docs/model-scheduling.md. None of these overlap or interact with packages/local-installation or apps/desktop. No code changed since the previous review, so there was nothing new to review.

Findings

None.

Verified as fine

  • The three PR commits are byte-identical to the previously reviewed ones (git range-diff).
  • The rebase dropped the merge commit, so history is linear on top of main.
  • The commits that arrived from main do not touch any file this PR changes.

Process notes

  • The branch is up to date with main. Merge state is BLOCKED only on pending checks and the required review.

CI

  • changes, repository-gate: pending at review time.
  • Analyze (actions), CodeQL: pass.
  • No tests run locally. Nothing changed since the previous review, and this PR does not touch apps/computer/.

Automated review by Claude Code


Automated review of cdafb8a by Claude Code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants