Skip to content

Error instead of silently failing when browsing an unreachable environment#4590

Open
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:t3code/error-on-unreachable-environment-browse
Open

Error instead of silently failing when browsing an unreachable environment#4590
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:t3code/error-on-unreachable-environment-browse

Conversation

@colonelpanic8

@colonelpanic8 colonelpanic8 commented Jul 26, 2026

Copy link
Copy Markdown

What Changed

The add-project browser (⌘K → Add project → Local folder) resolves paths on the selected environment's filesystem via filesystemEnvironment.browse, but never read that query's error. This adds an explicit availability check and fails closed instead of failing silently.

  • New pure resolveBrowseAvailability in CommandPalette.logic.ts maps the environment's connection phase (available / offline / connecting / reconnecting / error / connected) onto an Available | Unavailable { message } verdict, carrying the underlying reason when there is one. It is keyed on reachability only — a browse failure on a reachable host means the path does not exist yet (the server returns read_directory_failed for a missing parent), which is exactly the Create & Add case, so it must not gate submission.
  • The browse RPC is no longer issued at all unless the environment is connected.
  • canSubmitBrowsePath is false when unavailable, which disables the Enter path, the submit button, and the clone-destination step (they share it). This also withholds the "create this folder and add it as a project" affordance, so the UI can never offer to create a directory on a host it failed to inspect.
  • The reason renders in the results empty state instead of a blank list.
  • handleAddProjectForEnvironment refuses with an explicit toast when the target environment is not connected, reusing the same verdict so the toast states the reason rather than a bare status word. This is the layer that covers the desktop folder-picker entry point, which bypasses the browse UI entirely.
  • ChatComposer's @-path mention search dropped its error the same way, so an unreachable environment rendered "No matching files or folders." That reads as a definitive answer to a question we never got to ask; it now surfaces the actual failure.

Why

Browsing resolves paths on a machine that may not be reachable, so "no entries" and "could not ask" are very different answers that looked identical.

Reproduced against a paired remote environment that was then killed: the palette rendered an empty Directories group indistinguishable from an empty directory, showed no reason anywhere, and left the Add button enabled. Pressing Enter did nothing at all — no toast, no error, no state change. The failure was entirely invisible.

FileBrowserPanel and FilePreviewPanel already render .error for project file reads, so the add-project browser and the composer path search were the outliers rather than the convention.

UI Changes

Both captures are the same flow — Add project → the remote environment → Local folder~/ — against a paired remote whose backend was killed.

Before After
Add-project browser against an unreachable environment: an empty "Directories" group, no error text, and an enabled Add button Same flow showing "Reconnecting to railbird-sf. Reason: Failed to fetch remote environment endpoint ..." with the Add button disabled

Before: empty list, no reason, Add enabled, Enter silently does nothing.
After: Reconnecting to railbird-sf. Reason: Failed to fetch remote environment endpoint http://127.0.0.1:47319/.well-known/t3/environment (HttpClientError: Transport error ...), Add disabled.

Verified live in the web app against two t3 serve instances: the second was paired as a remote environment, confirmed Connected, then killed to produce the unreachable state.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • No animation/interaction change, so no video

Validation:

  • vp test run apps/web/src/components/CommandPalette.logic.test.ts (14 tests, 9 new)
  • vp run --filter @t3tools/web typecheck (no errors in changed files)
  • vp lint on all four changed files (no new findings)
  • vp fmt --check on all four changed files

Note

Medium Risk
Changes add-project and composer path flows that depend on remote environment connectivity; behavior is guarded by tests and scoped to reachability, but incorrect gating could block legitimate folder creation or allow silent failures.

Overview
Unreachable remote environments used to look like empty directories in add-project browse and @ path search. This PR fails closed on connection reachability while keeping Create & Add working on connected hosts when the path does not exist yet.

resolveBrowseAvailability maps environment connection phase (and optional error text) to Available or Unavailable with a user-facing message. It intentionally ignores browse/RPC failures when phase is connected, so missing-parent read_directory_failed still allows folder creation.

The command palette skips filesystemEnvironment.browse unless the selected environment is connected, hides directory listings when unavailable, shows the reason in the empty state, and disables path submit (Add / Create & Add / clone destination). handleAddProjectForEnvironment (including desktop folder picker) shows the same message in an error toast instead of attempting the add.

Chat composer @ path mentions now show workspaceEntries.error in the menu empty state instead of “No matching files or folders.” when the lookup failed.

Unit tests cover resolveBrowseAvailability, including connected + browse error staying available.

Reviewed by Cursor Bugbot for commit fc2294e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Show error instead of silently failing when browsing an unreachable environment

  • Adds resolveBrowseAvailability in CommandPalette.logic.ts that returns an Available/Unavailable verdict with a user-facing message based on the environment's connection phase and error reason.
  • The command palette skips filesystem browse queries, hides browse lists, and disables path submission when the selected environment is not connected.
  • Adding a project via handleAddProjectForEnvironment now shows a toast error with a specific availability message instead of attempting an operation that will fail.
  • The chat composer's path search menu surfaces the actual error message instead of the generic "No matching files or folders."

Macroscope summarized fc2294e.


Review follow-up (429e392)

Bugbot caught two issues on the first commit; both are fixed.

Browse errors blocked folder creation (high). Real regression, and my fault for unit-testing that branch instead of exercising it. WorkspaceEntries.browse only swallows EACCES/EPERM to an empty listing — ENOENT propagates as read_directory_failed. Treating that as Unavailable made canSubmitBrowsePath false, so Create & Add could never create a folder that did not already exist. Availability is now keyed on connection phase alone. Verified end to end: on a connected environment ~/brand-new-folder-xyz/ shows an enabled Create & Add, and Enter creates the directory and adds the project. Regression test added pinning connected + browse error to Available.

Toast used status, not reason (medium). connectionStatusText renders the bare phase word, so the refusal really did read "Failed to add project — Available". The guard now reuses the availability verdict and reads " isn't connected."

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6249600c-6bb6-4f40-b576-b6e5de38fb1e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 26, 2026
Comment thread apps/web/src/components/CommandPalette.tsx
Comment thread apps/web/src/components/CommandPalette.logic.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved

This is a well-scoped UX improvement that adds proper error messages when browsing an unreachable environment. The changes are self-contained with comprehensive test coverage, involving pure logic additions and UI error display without modifying core business logic.

You can customize Macroscope's approvability policy. Learn more.

colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
…rowsing an unreachable environment)

# Conflicts:
#	apps/web/src/components/CommandPalette.logic.test.ts
#	apps/web/src/components/CommandPalette.tsx
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
Adds pingdotgg#4590 to the thread-picker group rather than the main
manifest: it edits the same CommandPalette trio, and specifically the
browse/submit region that pingdotgg#4257 and pingdotgg#4426 also touch, so the combination
resolves once against upstream main instead of being re-derived every refresh.

Group rebuilt (reproduce) to b15922f and repinned in stack.toml; main stack
refreshed. The group rebuild reproduced tree bad6ae3 across two independent
runs, and the assembled tree keeps every pingdotgg#4590 gating site plus the composer
path-search error.

The one non-mechanical resolution: the group had already extracted the palette
empty-state ternary into `contextualEmptyStateMessage` +
`resolveCommandPaletteEmptyStateMessage`, so pingdotgg#4590's inline branch was rewritten
onto that helper instead of taking either side verbatim.
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
…rowsing an unreachable environment)

# Conflicts:
#	apps/web/src/components/CommandPalette.logic.test.ts
#	apps/web/src/components/CommandPalette.tsx
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
… and pingdotgg#4599

The published build already carried pingdotgg#4605 and pingdotgg#4599 -- stack-build-info.json on
t3code/stack lists both -- but the manifest edits that admitted them were never
committed here, so the recorded intent had drifted from the artifact and a
rebuild would have dropped two features. Restore them at the OIDs the published
build resolved, then append pingdotgg#4624.

pingdotgg#4624 is closed upstream without merging; carried because the replay cost is
real on this phone. Its head carries a fix past the PR: the 200ms code-highlight
debounces could never fire, because a growing code block's React key encodes its
source offsets and the block remounts on every delta.

Also refresh two pins that the artifact had moved past (the thread-picker group
and local/nix-flake), and re-audit the exception set: the group rebuild at
c5a4a73 shrank the CommandPalette losses on pingdotgg#4263/pingdotgg#4258/pingdotgg#4426 from 29/28/19
to 3/3/4. pingdotgg#4590's waiver is rewritten -- its 32 missing lines are an unbuilt
branch revision, not a resolution loss, because the group is still pinned at the
older member head.
colonelpanic8 and others added 2 commits July 27, 2026 03:26
…ble environment

The add-project browser resolves paths on the selected environment's
filesystem, but never read the browse query's error. Against an environment
the client cannot reach, the result is a silent dead end: the directory list
renders an empty "Directories" group that is indistinguishable from an empty
directory, no reason is shown anywhere, and the submit button stays enabled —
pressing Enter then does nothing at all, with no feedback.

Add `resolveBrowseAvailability`, which maps the environment's connection
phase plus any browse error onto an explicit availability verdict, and use it
to fail closed:

- the browse RPC is not issued unless the environment is connected;
- `canSubmitBrowsePath` is false when unavailable, disabling the Enter path,
  the submit button, and the clone-destination step. This also withholds the
  "create this folder and add it as a project" affordance, so the UI can
  never offer to create a directory on a host it failed to inspect;
- the reason renders in the results empty state instead of a blank list.

`handleAddProjectForEnvironment` also refuses with an explicit toast when the
target environment is not connected, which covers the desktop folder-picker
entry point that bypasses the browse UI entirely.

The composer's @-path mention search dropped its error the same way, so an
unreachable environment rendered "No matching files or folders" — a
definitive-sounding answer to a question we never got to ask. It now surfaces
the actual failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…se errors

Review caught a regression in the previous commit: treating any browse error as
Unavailable made canSubmitBrowsePath false, which killed Create & Add on a
perfectly reachable host.

The server returns read_directory_failed whenever readdir on the parent fails,
and only EACCES/EPERM are swallowed to an empty listing -- ENOENT propagates.
So typing a folder that does not exist yet, which is precisely what Create &
Add exists for, produced a browse error and the new gate refused to submit.
Verified end to end: on a connected environment "~/brand-new-folder-xyz/" now
shows an enabled "Create & Add", creates the directory, and adds the project.

Availability is now derived from the connection phase alone. A browse failure
on a reachable host says nothing about whether the environment can be reached,
so it no longer participates.

Also drop connectionStatusText from the add-project guard. It renders the bare
phase word, so refusing an add against a non-connected environment produced
"Failed to add project — Available". The toast now reuses the same availability
verdict as the browser and reads "<environment> isn't connected."
@colonelpanic8
colonelpanic8 force-pushed the t3code/error-on-unreachable-environment-browse branch from 429e392 to fc2294e Compare July 27, 2026 11:15
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
…rowsing an unreachable environment)

# Conflicts:
#	apps/web/src/components/CommandPalette.logic.test.ts
#	apps/web/src/components/CommandPalette.tsx

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fc2294e. Configure here.

}),
);
return;
}

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.

Missing env skips refuse guard

Low Severity

When environments.find misses the target id, targetAvailability stays null and the new refuse path never runs, so add continues instead of failing closed. resolveBrowseAvailability already maps a null phase to an Unavailable verdict with a clear message, but that branch is skipped here.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fc2294e. Configure here.

}),
);
return;
}

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.

Guard blocks opening existing projects

Medium Severity

The new availability refuse runs before findProjectByPath, so an unreachable environment blocks reopening a project that already exists locally. That hits the desktop folder-picker path, which stays enabled while browse submit is disabled, and turns a local open into a Failed to add project toast.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fc2294e. Configure here.

colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
…rowsing an unreachable environment)

# Conflicts:
#	apps/web/src/components/CommandPalette.logic.test.ts
#	apps/web/src/components/CommandPalette.tsx
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
All 41 writable topics were rebased onto upstream b4680cb and force-pushed,
so every pin here moved. The thread-picker group was rebuilt at the new member
heads, which also clears the stale-pin gap that had pingdotgg#4590's newer revision
sitting in no build.

Two cumulative branches -- t3code/client-suite (37 commits) and
t3code/unify-environment-selection (41) -- were replayed as a single
content-identical commit each rather than rebased commit-by-commit. They rewrite
their own files repeatedly, so each intermediate replay conflicted against a
state present in no final tree and with no oracle to resolve it against; that is
how the sidebar host-name and accent-color features were lost once before.
Applying base..tip instead reproduced both contributions exactly -- verified line
for line: 113 files / 6269 added lines and 131 files / 6686 added lines, both
unchanged. Old heads are preserved under refs/refresh-backup/.

Removed stack/patches/t3code-command-palette-logic-helpers.patch from the group
manifest. It predated the members carrying those helpers themselves: it deleted
shouldHandleCommandPaletteShortcut and shouldClearAddProjectEnvironmentOnPop and
stripped isLoaded from resolveNewThreadOnIntent, while adding three helpers
nothing in the assembled tree calls. Once the members were rebased, the
assembled CommandPalette.tsx imported both deleted symbols, and the web build
failed with two MISSING_EXPORT errors. Dropping it also shrank the audited
CommandPalette losses on pingdotgg#4263/pingdotgg#4258/pingdotgg#4426 from 10/7/7 to 3/3/4.

Added patches/t3code-dedupe-environment-icon.patch. client-suite and
unify-environment-selection each add the same EnvironmentIcon helper to
BranchToolbarEnvironmentSelector.tsx; git merges both without conflict and the
result fails to parse. Genuinely stack-emergent, so it cannot live on either
topic. Only the esbuild syntax gate catches this class -- the content audit
looks for missing lines, not duplicated ones.

pingdotgg#4624 is now a branch-linked topic with no `pr` key: it was closed upstream
without merging, so the branch is authoritative and the entry must not be
dropped on account of the PR's state.

Ladder: syntax gate clean, content audit exits 0, nix build exits 0, smoke
check exits 0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant