Skip to content

fix: stop a failed PM2 read from looking like an externally-started llama-server - #4804

Merged
atomantic merged 1 commit into
mainfrom
claim/issue-4780
Aug 22, 2026
Merged

fix: stop a failed PM2 read from looking like an externally-started llama-server#4804
atomantic merged 1 commit into
mainfrom
claim/issue-4780

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

getLlamaServerStatus returns managed: null when the PM2 read failed — deliberately not the same as false ("somebody else started this daemon") — but it nulled config on the same failed read. Every caller guarding on !status.managed || !status.config?.model therefore treated "could not tell" exactly like an external process, and the tuning relaunch refused with "llama-server was started outside PortOS", pointing a user who owns the daemon at a process that does not exist.

Since #4759 an untuned assessment relaunches too, so the misdiagnosis costs data: the refusal is filed applied: false, which getAssessmentReport drops from scorable, which removes the model's backend-defaults baseline — the row compareTunings ranks every tuned reading against — from the comparison table. The !running exit also cleared preTuningConfig, so the baseline could not be restored even once PM2 answered again.

  • Keep the last known launch line on a failed read, so managed: null is actionable separately from managed: false.
  • Retry the PM2 read a bounded number of times (readLlamaServerStatusRetrying, 2 retries) before letting "could not tell" decide anything — fetchJlist caches successes only, so each attempt is a genuine re-read.
  • Refuse an unreadable PM2 as itself (retryable: true, "could not read PM2") in relaunchLlamaServerWithTuning, captureLlamaServerConfig and restoreLlamaServerConfig, checked ahead of any branch that discards state. The refusal stays — PortOS must not restart a process it cannot prove it owns — it just stops blaming an external process for it.
  • Capture on an unreadable read rather than capturing nothing: a sweep clears the user's launch flags either way, and a capture is a read whose only consumer re-checks ownership itself.
  • Render the three states distinctly on the LLMs page instead of calling an unreadable server "Running as external process".

relaunchLlamaServerWithAlias already drew this distinction; the tuning path now matches it, and mtplxServerManager remains the reference for the split.

Test plan

  • cd server && npm test — full suite green (32,714 passed). The two routes/imageGen.* suites that time out under parallel load pass in isolation and are unrelated to this change.
  • cd client && npx vitest run src/components/settings/LocalLlmTab.test.jsx — 41 passed.
  • Every new test was run against origin/main first and fails there:
    • status keeps / drops the launch line correctly for a failed vs. successful-but-empty read;
    • the tuning relaunch refuses an unreadable PM2 as retryable with a "could not read PM2" reason and never mentions an external process, having re-read more than once (bypass probe on the retry);
    • a single failed read no longer blocks the tuning — it is applied after the retry;
    • the pre-tuning baseline survives an unreadable PM2 and is still restorable once PM2 answers;
    • capture returns the last known line, and restore refuses with the honest reason;
    • the LLMs page shows the unreadable state instead of "Running as external process".

Closes #4780

…lama-server (#4780)

`getLlamaServerStatus` set `managed: null` for a PM2 read that FAILED — but
nulled `config` on the same read, so every caller guarding on
`!managed || !config?.model` got the same answer as for a daemon somebody else
started. The tuning relaunch then refused with "llama-server was started outside
PortOS", pointing a user who owns the daemon at a process that does not exist.

Since #4759 an UNTUNED assessment relaunches too, so this cost real data: the
refusal is filed `applied: false`, which drops the reading from `scorable`, which
removes the model's backend-defaults BASELINE — the row every tuned reading is
ranked against — from the comparison table. The `!running` exit also cleared the
pre-tuning launch line, so the baseline could not be restored even after PM2
answered again.

- keep the last known launch line on a failed read, so `managed: null` is
  actionable separately from `managed: false`
- re-read PM2 a bounded number of times before letting "could not tell" decide
  anything, absorbing the transient hiccup that caused this
- refuse an unreadable PM2 as itself (`retryable`, "could not read PM2") in the
  tuning relaunch, the capture and the restore, ahead of anything that discards
  state — the refusal is still the safe direction, it just stops lying about why
- capture the launch line on an unreadable read rather than nothing, so a sweep
  that clears the user's flags still has a record of what they were
- render the three states distinctly on the LLMs page instead of calling an
  unreadable server external
@atomantic
atomantic merged commit e1ccfcc into main Aug 22, 2026
7 checks passed
@atomantic
atomantic deleted the claim/issue-4780 branch August 22, 2026 02:06
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.

getLlamaServerStatus collapses "PM2 unreadable" into "not managed", so a transient read failure loses a model's baseline assessment

1 participant