Skip to content

fix(tui): use server home for remote directory changes - #55203

Closed
nakulmaurya wants to merge 23 commits into
anomalyco:devfrom
nakulmaurya:remote-home
Closed

nakulmaurya wants to merge 23 commits into
anomalyco:devfrom
nakulmaurya:remote-home

Conversation

@nakulmaurya

@nakulmaurya nakulmaurya commented Oct 13, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #55171

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Use the server's home for /cd ~, ~/... autocomplete, and recent directory paths, rather than the attached client's home. Add optional paths.home to server info, cache it per client, and regenerate API clients. Older servers retain the existing fallback; session moves still send raw input for server-side resolution. Following review, only tilde input awaits server-home metadata. Plain directory changes, session moves, and relative directory autocomplete no longer depend on server.info succeeding. Home-relative autocomplete still fails closed when server metadata cannot be read, rather than substituting a remote client home.

How did you verify your code works?

  • Kept one session-tilde move regression with server.info returning 503. It awaits the request directly, without the two-second race or duplicate command cases.
  • Added focused coverage for relative autocomplete during metadata failure. It fails before the fix and passes afterward. Removed the speculative empty-home normalization and two HTTP boundary cases as requested in the latest agent review, eliminating the incorrectly typed Bun.serve handler.
  • The real-server/client-context check still verifies that server home wins over a different client paths.home.
  • Final full TUI suite after the latest review cleanup: 1,503 passed, 2 skipped, 0 failures. Three server-process tests also pass.
  • Protocol and website generated-file checks pass. Client regeneration produces no diff. Website typecheck and production build pass.
  • Regenerated all three OpenAPI snapshots. Full regeneration also refreshes Provider.Encoding entries already present in the schema but missing from the old snapshots.
  • Local TUI typechecking was killed by the sandbox memory limit, including a resource-bounded attempt. The root bun run check still stops at an oxlint allocator panic. These are not counted as passing checks.

No model/API credits used. Native macOS-to-Linux interactive testing was not performed.

Nonblocking follow-up

Session recent-directory bookkeeping can still use the client-home fallback before server-home metadata is cached. The move itself remains server-resolved. Other home-relative display changes remain outside this fix.

Screenshots / recordings

No visual layout changes. Regression coverage uses the real TUI renderer and HTTP server with different client/server home directories.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #47665

Public API: adds optional paths.home to the server.info response, OpenAPI spec and generated client types (backwards compatible)

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The fix works. I ran the #55171 reproduction (attached TUI with a different home than the server) from source. On the base branch, /cd ~/ autocomplete fails and /cd ~ errors with Location not found: <client home>. With this PR, autocomplete lists the server home and /cd ~ succeeds. The new server and TUI tests pass, typecheck is clean in tui, server and protocol, and regenerating the client gives no diff.

Public API: adds an optional paths.home field to the server.info response (ServerInfo in protocol, and the generated ServerInfo/ServerInfoOutput client types). This is backwards compatible: existing clients ignore the field, and the TUI falls back to its own home when an older server doesn't send it.

Concerns, inline:

  • /cd inside a session now depends on a successful server.info call, even for input without ~.
  • The new test can be trimmed.

Other home-relative displays (the prompt footer's location label, the open and workspace dialogs, the path-format context) still use the client home. They don't break anything and could be a follow-up.

Related: #47665

Before

Before

After

After

const sessionID = props.sessionID
const session = sessionID ? data.session.get(sessionID) : undefined
const sourceProjectID = session?.projectID ?? data.location.info()?.project.id
const home = await client.resolveServerHome().catch((error) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This makes every /cd wait for server.info() and abort with a toast if it fails, including inside a session, where session.move gets the raw input and the server already expands ~ itself. Could this lookup run only when the input starts with ~? Or, in the session branch, could a failure fall back instead of blocking the move? Then a server-info hiccup won't stop plain /cd ../foo.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 2ed36eb and covered by 82f5925. Only non-session tilde changes now await server-home metadata; plain /cd and all session moves bypass that dependency. Three rendered-command regressions use server.info = 503 and verify relative home-screen changes plus raw relative/tilde session.move payloads. They fail before this change and pass afterward.

expect(home).not.toBe("/Users/remote-client")
const completions = yield* Effect.promise(() => directoryAutocomplete(api.file, { directory: current }, "~", home))
expect(completions[0]).toEqual({ value: "~", absolute: home })
const destination = yield* Effect.promise(() => api.location.get({ location: { directory: home } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Most of this test calls helpers (directoryAutocomplete, location.get, directoryRecentValue) with a home passed in by hand, so it doesn't exercise the new /cd or autocomplete wiring. expect(setup.renderer.isDestroyed).toBe(false) and the /Users/remote-client/project assertion check nothing related to the fix. Could you cut it down to the core check, that resolveServerHome() returns the server's home rather than the client paths.home, and drop the rest?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in b78aa18. The real-server/client-context test now checks only that resolveServerHome returns the server home despite a different client paths.home. Removed the manually parameterized helper checks and renderer-liveness assertion. Command wiring is covered separately by the rendered regressions in 82f5925.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, confirmed in b78aa18: the test now checks only that resolveServerHome() returns the server home, which resolves this thread. The one open point is the separate comment on the new app-lifecycle.test.tsx cases (their size and the 2s sleep race).

@nakulmaurya

Copy link
Copy Markdown
Author

Review feedback addressed in 82f5925: metadata failures no longer block plain /cd or session moves, and the server-home context test is trimmed. Full TUI suite: 1,504 passed, 2 skipped; the three new command regressions fail before the fix and pass after it. Local typecheck has identical pre-existing diagnostics on the unmodified branch; bun run check stops at an oxlint allocator panic. The published tree matches the tested code. Could a maintainer approve the pending workflows and request a fresh agent review? Other home-relative display changes are left for a follow-up, as suggested.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both earlier points are resolved. /cd now waits on server.info only for home-screen ~ input; relative paths and session moves no longer depend on it. server-home.test.tsx is down to the one check that matters. I re-ran the #55171 reproduction on the new head: /cd ~/ autocomplete lists the server home and /cd ~ succeeds. The TUI tests and typecheck pass.

Public API: unchanged since my last review, still only the optional paths.home on the server.info response (backwards compatible).

One remaining point: the new app-lifecycle test is heavy for what it checks (inline).

Related: #47665

Before

After

After

import { createAppFixture } from "./fixture/app"
import type { PluginInfo } from "@opencode/client"

test.each([

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is about 60 lines of hand-mocked routes and session fixtures, plus a 2s Bun.sleep race, all to cover a guard of a few lines. The race timeout could get flaky on slow CI. I'd keep a single case (the session tilde move with /api/info returning 503) and wait on the request without the sleep, or drop the test. The fix doesn't really need it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Kept one session-tilde move case and removed the two-second race. It now awaits the move request directly. The separate short completion case covers the newly reported relative-autocomplete bug, not another copy of the command guard. Final TUI suite passes with 1,505 tests and 2 skipped.

@sghng

sghng commented Oct 13, 2026

Copy link
Copy Markdown

Independent verification + a few review findings from a parallel fix attempt of this bug (we confirmed the macOS→Linux /cd ~ repro end-to-end over tailscale, so thanks for getting to this — the server.info-based approach looks right, and the 503-must-not-block test is a nice touch).

A few things our review run caught that look like they apply here:

  1. The OpenAPI snapshot wasn't regenerated. packages/protocol/src/groups/server.ts changed, but packages/protocol/openapi.json (and the services/www/openapi.json + services/www/public/openapi.json copies) still show ServerInfo.paths with only tmp. On this branch, cd packages/protocol && bun run check:generated fails with "Generated OpenAPI document is stale" — worth running bun run generate in packages/protocol and services/www before merge (CI appears to gate the client generation and the www copy-consistency, but not protocol freshness, so this can otherwise merge stale).

  2. Directory autocomplete is fully disabled when /api/info fails (autocomplete.tsx): resolveServerHome().catch(() => null) → home === null skips directoryAutocomplete entirely, so a local server with a failing /api/info loses completion even though paths.home would be correct there. Blocking is the safer choice for a remote server (a client-home fallback would list nonexistent paths), but it might deserve a comment or a fallback to paths.home when the connection is local. Related: the first completion in a directory now blocks on a network round-trip (cached afterward).

  3. Empty-string home wins the fallback chain: home ?? paths.home treats "" as authoritative — on the workerd runtime os.homedir() returns "", so ~ would expand against nothing. || instead of ?? falls through to the client home in that case.

  4. Minor transient on the session path: client.serverHome() can still be undefined right after connect, so directoryRecents.touch briefly records the client-home-resolved path until /api/info lands. Self-heals; only recents is affected since session.move receives the raw input and expands server-side (as your test asserts).

For what it's worth, we also hit and confirmed the same subtlety you avoided here: yielding Global.Service inside the per-request handler effect leaks the requirement through the middleware chain into ServerProcess.start — the group-generator yield is the right pattern. The optional field handles version skew in both directions cleanly too (excess-property stripping covers old clients).

@nakulmaurya

Copy link
Copy Markdown
Author

Addressed the remaining review feedback in a608e3a. The command regression is reduced to one session-tilde case with no sleep race. Regenerated protocol and website OpenAPI snapshots, fixed relative autocomplete during metadata failure, and normalized empty server-home values. Full TUI suite passes with 1,505 tests and 2 skipped; server-process tests, generated-file checks, and website typecheck/build pass. Local TUI typecheck is memory-limited, and the root check still hits an oxlint allocator panic. The published tree matches the tested tree. Session-recents warm-up and other display paths remain explicit nonblocking follow-ups.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still fixes #55171: I re-ran the reproduction on this head. /cd ~/ autocomplete lists the server home and /cd ~ succeeds. The TUI tests pass (40/40), but typecheck in packages/tui now fails because of the new server-home test (inline).

Resolved since last review: the app-lifecycle tests are now small and don't use the sleep race, and relative autocomplete no longer waits on server.info. The refreshed OpenAPI snapshots, including the Provider.Encoding entries, are needed: check:generated fails on the base and passes here.

Public API: adds the optional paths.home to the server.info response, the OpenAPI spec, and the generated ServerInfo/ServerInfoOutput client types. This is backwards compatible.

Related: #47665

Before

After

After

test.each([undefined, ""])("treats an unavailable server home (%j) as missing", async (home) => {
const server = Bun.serve({
port: 0,
fetch: createFetch(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This breaks bun run typecheck in packages/tui (TS2322): createFetch(...).fetch has the client fetch(input, init) signature, not Bun.serve's (req, server) handler. Neither server code path sends an empty home, so this test and the || undefined in client.tsx aren't really needed. I'd drop them, which also fixes the typecheck.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed both speculative boundary cases and empty-home normalization as suggested. The real-server regression and relative autocomplete coverage remain

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, confirmed in 92cb614: the empty-home test and the || undefined in client.tsx are gone, and server-home.test.tsx no longer has the Bun.serve call behind the TS2322 error. I only read the code and didn't re-run typecheck, so it's worth confirming that bun run typecheck in packages/tui passes on this head.

Removed a test case that checks for unavailable server home handling.
@nakulmaurya

Copy link
Copy Markdown
Author

Addressed the latest agent review in 92cb614: removed the speculative empty-home handling and incorrectly typed boundary tests. Retained the real-server and relative-autocomplete regressions

@sghng

sghng commented Oct 25, 2026

Copy link
Copy Markdown

Bump. This is such an annoying bug. It'll be greatly appreciated if maintainer can take action.

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tui(v2): /cd tilde expansion uses client home for remote servers

2 participants