Repository navigation
Description
Summary
Configured SSH hosts disappear from the desktop host suggestions when an Include pattern has a wildcard in a directory component. OpenSSH accepts the same configuration and resolves the host normally. This was reproduced against the production discovery API with isolated configuration files and a native OpenSSH control.
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/desktop
Steps to reproduce
- Create a temporary SSH home with
.ssh/teams/one/hosts.confcontainingHost fixture,HostName 192.0.2.10, andUser developer. - Write
.ssh/configasInclude teams/one/*.conf; calldiscoverSshHosts({ homeDir })and verify the alias appears. - Change only the Include to
teams/*/*.conf; compare nativessh -G -Fwith the same production discovery call.
The isolated library reproduction is deterministic and failed in both recorded runs. The desktop UI was not launched; the UI consequence is source-only through the production discovery/IPC path.
Save the script below as repro-include.mjs and run bun repro-include.mjs /path/to/t3code from any directory. Prerequisites: a checkout of the stated commit with its locked dependencies installed, Bun, Git and OpenSSH. The script imports the production discovery API, creates every configuration input in an isolated temporary home, sets HOME/TMPDIR/T3CODE_HOME there, removes that home on exit, and leaves the checkout unchanged. It makes no SSH connection. Exit 1 means the defect reproduced, exit 0 means the expected behavior held, and exit 2 means setup or a control failed.
Reproduction script: repro-include.mjs
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { resolve, join } from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import { execFileSync, spawnSync } from "node:child_process";
const checkout = resolve(process.argv[2] ?? ".");
const scratch = mkdtempSync(join(tmpdir(), "ssh-include-repro-"));
let status = 2;
try {
console.log(`Bun ${Bun.version}; ${process.platform} ${process.arch}`);
console.log(execFileSync("git", ["--version"], { encoding: "utf8" }).trim());
const sshVersion = spawnSync("ssh", ["-V"], { encoding: "utf8" });
if (sshVersion.status !== 0) throw new Error("ssh version check failed");
console.log(`${sshVersion.stdout}${sshVersion.stderr}`.trim());
console.log(`Commit ${execFileSync("git", ["-C", checkout, "rev-parse", "HEAD"], { encoding: "utf8" }).trim()}`);
process.env.HOME = scratch;
process.env.TMPDIR = scratch;
process.env.T3CODE_HOME = join(scratch, "t3");
mkdirSync(join(scratch, ".ssh", "teams", "one"), { recursive: true });
writeFileSync(join(scratch, ".ssh", "teams", "one", "hosts.conf"), "Host fixture\n HostName 192.0.2.10\n User developer\n");
const require = createRequire(join(checkout, "packages/ssh/package.json"));
const Effect = await import(pathToFileURL(require.resolve("effect/Effect")));
const NodeServices = await import(pathToFileURL(require.resolve("@effect/platform-node/NodeServices")));
const { discoverSshHosts } = await import(pathToFileURL(join(checkout, "packages/ssh/src/config.ts")));
const run = () => Effect.runPromise(discoverSshHosts({ homeDir: scratch }).pipe(Effect.provide(NodeServices.layer)));
const config = join(scratch, ".ssh", "config");
writeFileSync(config, "Include teams/one/*.conf\n");
const control = await run();
console.log(`Literal-directory control: ${JSON.stringify(control.map(({ alias }) => alias))}`);
if (!control.some(({ alias }) => alias === "fixture")) throw new Error("Control failed");
writeFileSync(config, "Include teams/*/*.conf\n");
const expected = execFileSync("ssh", ["-G", "-F", config, "fixture"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
const hostname = /^hostname (.+)$/m.exec(expected)?.[1];
console.log(`OpenSSH wildcard-directory hostname: ${hostname}`);
if (hostname !== "192.0.2.10") throw new Error("OpenSSH contract control failed");
const actual = await run();
console.log(`T3 wildcard-directory aliases: ${JSON.stringify(actual.map(({ alias }) => alias))}`);
status = actual.some(({ alias }) => alias === "fixture") ? 0 : 1;
} catch (error) {
console.log(`Setup error: ${error.message}`);
} finally {
rmSync(scratch, { recursive: true, force: true });
}
console.log(status === 0 ? "RESULT: expected behavior" : status === 1 ? "RESULT: defect reproduced" : "RESULT: inconclusive");
process.exitCode = status;Expected behavior
Host suggestions should discover configured aliases from valid Include glob paths, including patterns in directory components. OpenSSH ssh_config(5), Include permits glob wildcards in each pathname. The native OpenSSH control resolves this exact pattern. The desktop delegates discovery to the shared implementation in apps/desktop/src/ssh/DesktopSshEnvironment.ts#L85-L87.
Actual behavior
With Include teams/*/*.conf and a matching file containing Host fixture, discoverSshHosts returns no hosts. Changing only the directory component to the literal directory (Include teams/one/*.conf) returns the alias. Native ssh -G -F resolves the wildcard configuration to the configured address without connecting.
Impact: P2 - Users organizing valid SSH configuration into wildcard-selected directories cannot find those configured hosts in the picker. The failure is bounded to discovery; manually entering the alias is a source-verified workaround via the manual target path, and replacing the wildcard directory with the tested literal directory restores discovery in the reproduction. No remote connection or authentication failure is claimed.
The observation establishes discovery output for the synthetic inputs below. NOT_ENOUGH_INFO: an integrated desktop execution and native Windows/Linux reruns were not performed; those runs would establish surface/platform behavior beyond the source path.
Impact
Minor bug or occasional failure
Version or commit
main @ 0bb6eaa (0bb6eaa). Latest published stable release checked: v0.1.9; packaged release behavior was not tested.
Environment
Bun 1.4.2, macOS (darwin) arm64, Git 2.56.0, OpenSSH_10.5p1 / LibreSSL 3.3.6. The production API was imported with the checkout's installed locked dependencies. The existing focused config test file passes all six tests under Vite+ (runner reports v5.0.5).
Logs or stack traces
Evidence
Observed output from running the exact script above:
Bun 1.4.2; darwin arm64
git version 2.56.0
OpenSSH_10.5p1, LibreSSL 3.3.6
Commit 0bb6eaa546c91915efbd3f092088c694f3949709
Literal-directory control: ["fixture"]
OpenSSH wildcard-directory hostname: 192.0.2.10
T3 wildcard-directory aliases: []
RESULT: defect reproduced
Source: packages/ssh/src/config.ts#L216-L224 skips the Include whenever the parent directory has an SSH pattern. packages/ssh/src/config.ts#L103-L129 only expands basename wildcards. This explains the observed empty result; the source is cited as diagnosis, not as a proposed repair.
All fixture aliases, addresses, users, host-key payloads, and filenames are synthetic; no real SSH configuration or key was read. Evidence provenance: observed (discovery output) and static (desktop entry-point propagation). Local verification: reproduced. Reproduction completeness: complete for discovery; NOT_ENOUGH_INFO: full desktop connection behavior was not exercised and would require an integrated desktop run.
Expected source: the contract and source links under Expected behavior. Failure source: the isolated production-API run and pinned discovery source links above. Missing fact: integrated desktop and other-platform execution are not established; discovery itself is directly reproduced.
Restoration check
The wildcard-directory fixture must return the alias and configured hostname while the existing literal-directory control continues to pass. The reproduction should then print RESULT: expected behavior and exit 0.
Workaround
Use a literal directory in the Include path; this restores discovery in the control. Manually entering the configured alias also bypasses host suggestions (source-verified manual target path; no desktop connection was run).
References
- fix(ssh): expand Include globs in directory components #12431 — attempted this same directory-glob discovery fix but closed unmerged; its final maintainer comment requested rebuilding on current main with a newer model rather than declining the behavior. This report supplies a current minimal reproduction and makes no PR.
- fix(ssh): include hosts from wildcard filenames with line separators #35479 — addresses wildcard filenames containing Unicode line separators, not wildcard directory components.
Searches covered open and closed issues/PRs for SSH Include, directory glob, SSH wildcard, host discovery, and current open SSH Include PRs. The related open filename-matcher PR was read and does not remove the parent-directory skip.
Activity