Repository navigation
fix: reload node credentials and unify dashboard tables and settings - #359
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ilters Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…anti-vacuous-test rule)
…op reading as foreign Review round 1 of PR #359 (quote-verified): - MAJOR: the hot-reload gate validated only the pinned key before swapping, but loadConfig proves fields are STRINGS, not that they open a socket. A beat that swapped in a parseable-but-un-dialable serverUrl ("not a url", "", a junk nodeWsUrl) dropped the healthy connection, and the WebSocket constructor's throw has no catch between the dial and process exit (runConnection reject -> the loop -> runDaemon -> cli) — the service respawned into the same death, inverting the seam's stated worst-case-is-status-quo for exactly the writer that motivated it. The gate now validates the whole-bundle DIAL TARGET (parseable, ws:/wss:) beside the pin, deduped through the same warn; the refusal is not sticky (next legitimate write reconnects). Belt for the loop's missing catch was considered and left out on purpose: the gate closes the real entry, and a loop-catch changes the unwind paths the tests pin. - MINOR m1: persistLink mirrors its own §5 heal onto the live config but not onto liveIdentity, so every legacy self-heal logged a false "config changed; reconnecting" line at the loop seam (and correctness rode on R7's close ordering). The mirror now advances the tracker with it. - NIT n1 (same seam): a stale concurrent reader (beat in flight while the loop seam swapped) could have re-applied the superseded bundle for a beat; the reader now yields when the identity moved under its read. Tests: red on pre-fix, green on fix — the new un-dialable-serverUrl case and test (c)'s new false-line assertion; suite 913/913, biome + verify-types clean. Co-Authored-By: Claude <noreply@anthropic.com>
) * docs(style): agent means the AI agent, and status copy writes from the reader's seat The operator ruled (2026-10-10) that 'agent' confuses users when it names the node's background program: the daemon is the daemon (or node daemon), and a space-tight column header reads Version. STYLE.md gains the Daemon/Agent glossary rows, the ban on 'node agent' and an 'Agent' column for the daemon, and a new Status and problem sentences section: name what is true of the reader's machine, then the action that remains; no protocol verbs, no possessive riddles. The worked pair is the held-node line, before and after. .claude/rules/design-system.md carries the two-line pointer so UI agents see the ruling without opening the guide. Co-Authored-By: Claude <noreply@anthropic.com> * fix(copy): the node daemon is the daemon, everywhere a person reads it The word 'agent' now means only the AI agent a pane drives (ruling 2026-10-10, doctrine committed last commit). This applies it: - SPA: the Nodes table's Agent column becomes Version (the detail page already said Node version); the update card says the daemon restarts itself, and the held-node line becomes the operator-approved sentence 'The server will not accept this machine's Subshell daemon version. You can still update it from here.' replacing the refuse-its-agent original. - Node CLI, setup questions, probe warnings, update refusals, the 4406 exit line, maintenance/service/reset/env-forced messages: daemon, with the reader-seat rewrite where the old text described what the protocol layer did. 'KICKS' prose becomes 'disconnects'. - The daemon's own log file becomes logs/daemon.log. No migration: the plane's agent_log_read, the loopback dashboard, status --json, reset, and the desktop app's reveal-a-log ladder all fall back to agent.log until the daemon has written under the new name; tests pin the ladder on both the node and desktop side. Wire field names (agentLog, agentLogPath, agentVersion, kind 'agent') are identifiers and stay. - Desktop: app descriptions, the downgrade refusal, and the no-log hint. - docs: paths.mdx and security.md name the daemon log (and the quoted Nodes-dialog string is corrected to the label the dialog actually shows); node agent in-repo docs follow the file rename. Kept per the ruling: 'launchd agent' (Apple's term for server supervision) and every AI-meaning agent (Add an Agent, the harness card, Send prompt to agent?). Full suites green: node 905, server 3953, rust fmt/clippy/test, design tokens, prose. Co-Authored-By: Claude <noreply@anthropic.com> * test(cli-e2e): the reset scenario seeds and names daemon.log The bun suite stubs every service seam, so the renamed log file and the reworded reset line land invisible to it; the compiled scenario is the thing that drives the real binary. It seeded logs/agent.log and grepped 'left the agent', both of which this branch renames. Co-Authored-By: Claude <noreply@anthropic.com> * fix(web): 'node you can't see' reveals nothing more on hover The rail header (and the home tiles) carried the invisible node's full id as tooltip text, a kept-over reveal from the retired node pill whose point was telling machines apart. An operator, shown the uuid, asked the obvious question: if you can't see it, why is it shown? An id with no page behind it names nothing to act on; title now equals label, and both consumers' equal-title branch renders no tooltip. The cold arm keeps its reveal (a short id becoming the full one) because there the machine IS the reader's, merely not loaded yet. Co-Authored-By: Claude <noreply@anthropic.com> * fix(copy): the review round's findings — transfer lines, re-register refusal, OpenAPI prose Four minor, four nit, all quote-verified: the transfer refusals named the daemon 'the agent on node X' (the double meaning this PR exists to kill, sharpest where a harness reads the sentence about itself); the re-register refusal told the wizard's reader a row 'cannot re-register as an agent'; node-assistant.md still named agent.log as the first rung; the 4409 line said 'another subshell' for a daemon; the rollback lines kept the verb 'refused' the new STYLE section bans in status prose; 'agent node' in shipped OpenAPI descriptions reads as the AI sense. The readable-path docblock now names the one accepted switchover cost (a held poll offset the startup burst outgrows between polls skips bytes until a reload), and the Rust ladder test now also pins legacy-before-manager ordering. Co-Authored-By: Claude <noreply@anthropic.com> * fix(copy): the second round's findings — the rollback line, the MCP wrap, the schema twins All quote-verified. The successful-rollback log line still said 'the control plane refused' where its two failure siblings became 'would not accept'; the MCP wrap around the transfer-too-old error said 'the agent on the target node' three times around the (already fixed) interpolated server sentence; the transfer tool's own z.string description was the schema twin of the OpenAPI line round one renamed; three comments still promised the hover reveal the tooltip ruling removed; the 4406 tail asked for 'a newer subshell'; the restart fallback note kept 'refused'; the cli-e2e step title kept the old word beside its renamed log file; and the desktop package.json line this PR edited still shipped an em dash. The rollback line now has a test pin (daemon.test.ts, 4406 revert path): 'would not accept 9.9.9' present, 'the control plane refused' absent. The 4406 fixture strings now quote the server's real phrasing ('this node is'). Co-Authored-By: Claude <noreply@anthropic.com> * fix(changeset): the two SPA entries name @internal/server @internal/server-web is on changesets' ignore list; the SPA ships embedded in the server binary, and every prior SPA-only changeset (#356's stale- detection entry among them) names @internal/server. An ignored key here would ride NO release. Co-Authored-By: Claude <noreply@anthropic.com> * fix(copy): the third round's findings — the no-reason fallback and the dashboard verbs The 4406 revert fallback recorded 'the control plane refused it' into update-failed.json, which the loopback dashboard prints raw and which the rewritten rollback line interpolated — 'no reason was given' now, with a test that closes 4406 WITHOUT a reason and pins both the record and the log line. The dashboard's three error fallbacks ('The update was refused' et al) became plain outcomes, matching the update.ts note round two fixed. The log-file test restores the preload's config home instead of deleting the variable, so a test added below it cannot resolve the developer's real ~/.config/subshell. Co-Authored-By: Claude <noreply@anthropic.com> * fix(copy): the fourth round's findings — transfer_nodes' own description The MCP tool's shipped description told harnesses (and the humans reading them) that transfers run 'between two AGENT nodes', and one HTTP refusal in the same route family said it plainly - while the refusal it quotes about itself now says 'the daemon'. Both re-worded to machine nodes; the route's own docblock follows its text. Co-Authored-By: Claude <noreply@anthropic.com> * fix(copy): the fifth round's findings — the broken pin, the docs twins, and three strays The transfer-route test pinned toContain('agent nodes'), a shorter fragment than the message the last commit re-worded: it failed the moment that shipped (9/9 now, on 'machine nodes'). The public MCP tools page duplicated the server description's two sentences verbatim, so it moved with them. Three OpenAPI prose sites join the swept class ('an agent node' -> 'a machine node', 'an enrolled agent' -> 'an enrolled machine node', per the models.ts and reregister precedent this PR set). The Subshell Server app's reset confirmation - the app no sweep had visited - says 'a node agent on this machine keep running'; that is the node daemon. And the dashboard's maxBytes schema line says agent log beside a file this PR renamed. Co-Authored-By: Claude <noreply@anthropic.com> * chore(changeset): the Server app's reset dialog rides desktop-server The reset confirmation that gained 'node daemon' renders inside Subshell Server's window; no entry named @internal/desktop-server, so the app would have carried a wording change with no version. Co-Authored-By: Claude <noreply@anthropic.com> * style(rust): the two test edits in the shapes CI's rustfmt wants cargo fmt --check on the runner re-wrapped an assert_eq! into one line and re-split an assert! into three; the local toolchain accepted both prior shapes, so rust:check stayed green while CI's fmt gate saw the diff. Content unchanged - wrapping only. Co-Authored-By: Claude <noreply@anthropic.com> * fix(copy): apply conversational style across product and docs * test(e2e): follow fixed copy and selected setup addresses --------- Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(web): simplify the nodes table and show owners * fix(web): remove the nodes actions column label * test(e2e): check the nodes table and status dots
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(ui): explain unrestricted directories and edit permissions * docs(ui): use approved directory rules wording
* docs(website): align copy with the content guide * docs(website): distinguish hosting security and sharing * fix(website): balance the self-hosted section layout * fix(website): simplify hosting details and correct GitHub mark * docs(website): remove redundant installation link
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Explain disabled controls across the app * fix: show update progress immediately and highlight service restrictions * fix: address disabled-control review findings * fix: address second review pass on disabled controls Spell e-mail in the mobile sign-in reason, and neutralize the devices-strip note that accused a still-loading entry of view-only access. Narrow the design guide's Help text rule to inline explanations so the tooltip popup amendment stops contradicting it, and mirror the split in the loaded rules summary. Document the tooltip-latch remount tradeoff in all three button copies, and drop the blocker paragraph's now-unreferenced id.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Re-registering a node could leave its running daemon dialing with stale credentials. The daemon now reloads connection settings before dialing and on each heartbeat, preserving running subshells. The dashboard also gains consistent, responsive tables and page spacing.
Changes
Validation
Compatibility notes
A live server-address change can disconnect the old control plane before its command reply arrives, so that plane may report a timeout even when the change succeeded. The legacy
restartRequiredresponse field remains unchanged for compatibility; the daemon applies the new address without requiring a restart. Credential-triggered redials use the existing reconnect backoff.