mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
8b7a674d22
* ✨ feat(cli): add `lh doctor` to diagnose the whole chain in one command Diagnosing a broken `lh` today means knowing which layer to suspect: `status` answers only "does the gateway accept this machine", `whoami` only "who am I". Everything else — a node too old for the CLI's own imports, a device gateway that belongs to another deployment, a workspace scope saved under a different account, a provider with no key — surfaces much later as an error that names the symptom rather than the cause. `lh doctor` walks the chain in order and stops reporting derived symptoms once it finds a root cause: runtime → endpoints → credentials → scope → run context → server → device → execution - Profiles: bare `doctor` runs the core layers (fast, cheap); `--profile connect | agent | hetero | selfhost | all` adds the deeper ones. - `--offline` skips everything that touches the network, `--deep` opts into the costly checks, so a diagnostic never spends money by default. - `--json` prints the same report object the terminal renderer consumes, one entry per check with `status`, `detail`, `evidence`, `fix` and — for skips — the upstream check or flag that excluded it. - Exit code contract: 0 unless something failed, `--strict` promotes warnings. - `--fix` applies only the repairs a check knows how to make (stale workspace scope, unparseable settings.json, loose permissions, leftover daemon state) and re-runs the check so the report shows the repaired state. - `--deep --agent <id|slug>` starts a real run and reports tokens and cost, proving the chain end to end instead of inspecting it. Also extracts the credential precedence (`LOBEHUB_JWT` > explicit flags > `LOBEHUB_CLI_API_KEY` > stored login) into `pickAuthSource`, shared by `resolveToken`, the tRPC client and doctor — it was spelled out twice and had already drifted on which env names it accepts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): stop doctor from leaking proxy credentials or repairing healthy state Five findings from an independent review of the doctor series, all real: - `--fix` deleted the pid/status files of a daemon that was merely reconnecting, orphaning a live process `connect stop` could no longer find. Repairs now run only for the finding a check marks `evidence.repairable`, and each repair re-checks which state it was handed. The runner skips repairs entirely for findings without that marker, so a warning with nothing to undo no longer reports a failed repair. - Proxy env vars were copied into the report verbatim. `http://user:pass@host` is a normal way to configure one, and the report is meant to be pasted into an issue — URL credentials are now redacted. - A self-hosted server with the default device gateway failed `endpoints.resolution`, which skipped reachability, credentials, identity and every agent check behind it. An installation that only uses the HTTP API needs no device gateway, so this is a warning; the device profile's own handshake still fails concretely. - `--fix` dropped a valid saved workspace scope whenever `LOBEHUB_WORKSPACE_ID` overrode it for one run, silently moving the user to personal scope once the override went away. - `getAiProviderList` reports a provider's toggle, not whether a key is stored, so an enabled-but-keyless provider was reported as ready and the run then died with `InvalidProviderAPIKey` — the exact failure the check exists to pre-empt. Agent readiness now reads that one provider's key vault and names where the key comes from, saying "not verified" rather than passing when a restricted API key cannot see vaults at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 📝 docs(cli): record why the node check cannot catch a too-old runtime Closing that gap means changing how the CLI is launched, which is not this command's to change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): keep doctor from crashing, over-claiming, or leaking identifiers Ten findings from a second independent review, all confirmed: - A socket failure reaching the gateway arrives as an EventEmitter `error` event, which `connect().catch()` cannot see. With no listener the whole report died with "Unhandled error" instead of reporting a failed handshake — verified by removing the new listener and reproducing the crash against a refused port. - Server, gateway and proxy URLs are redacted at the report boundary, not just in the proxy variable: a basic-auth URL in `LOBEHUB_SERVER` used to print its password in `--json`. Gateway error text also quoted the WebSocket query string, carrying hostname, device id and user id; the account email is now masked too. The raw values still flow to whatever connects with them. - "Enabled on the server" no longer claims a key exists. `deepseek` is enabled unconditionally in the server's own config, and `ENABLED_OPENAI` / `ENABLED_OLLAMA` default to true, so the report now says the server reports it enabled and that server-side keys are invisible from here. - A provider holding only a `baseURL` is a local runtime (Ollama, LM Studio), not a missing key: it warns instead of failing, so it no longer blocks the deep round trip through `dependsOn`. - The round trip only reads "no longer tracked" as success once it has seen the run live. The server also answers null when there is no state at all, which made the check incapable of failing. - A saved workspace scope is only repairable when the account is identified and differs. Under `LOBEHUB_CLI_API_KEY` there is no readable subject at all, and `--fix` was deleting a selection that was still valid for the stored login. - The settings repair no longer overwrites an existing `.bak`. - The update check bounds its own registry request: the runner's timeout would otherwise surface as a failure and a non-zero exit, for something this check only ever reports as a warning. - The command sets `process.exitCode` instead of calling `process.exit`, which does not wait for stdout to drain and can truncate a piped JSON report. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): redact the server URL the workspace-scope check reports Sweeping every check's evidence for an injected credential — the way the acceptance round does — turned up one the previous pass missed: the scope check copies the resolved server URL verbatim, so a basic-auth self-hosted URL put its password into `--json`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): make the URL redactor linear instead of backtracking CodeQL flagged js/polynomial-redos: matching the URL lazily and then requiring a `?` backtracks across the remainder of the string at every starting position, so an error message with many `ws://` runs takes quadratic time. Tokenise first and decide per token — same output, no backtracking, with a test that fails if the quadratic shape comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): scrub the doctor report centrally and stop it lingering after exit Seven findings from a third independent review, all confirmed: - Redaction had leaked in a third place: Node's fetch error quotes the full request URL, and capability and daemon evidence copied gateway URLs as-is. Per-check redaction had now missed an instance in each of three rounds, so the runner scrubs every result — detail, fix, evidence, repair messages — before it enters the report: URL userinfo, WebSocket query strings and email addresses, by linear token scanning rather than a backtracking pattern. - With `process.exit` gone, the process waited for the event loop to empty, so a request abandoned by a check timeout, or the registry check's uncleared timer, kept it alive — up to the full `--timeout` after the report printed. The command now exits from a stdout write callback: after the output has drained, and no later. - The registry check clears its timer and gets a runner budget that always outlasts its own deadline; a sub-second `--timeout` no longer turns its warning into a failure. - A settings.json that cannot be read is no longer classified as corrupt and offered to `--fix`, which moved a possibly valid file aside unparsed. The repair also re-confirms the parse failure before moving anything. - A run that stops being tracked is inconclusive even after it was seen running: the server returns null for lost state too, so only `isCompleted` proves the round trip. - A live daemon that has not reported a connection state is a warning, not "connected as device unknown". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>