Skip to content

Fix socket connection issues#21

Merged
PabloZaiden merged 1 commit into
mainfrom
fix-devbox-up-old-tmp-socket
Apr 15, 2026
Merged

Fix socket connection issues#21
PabloZaiden merged 1 commit into
mainfrom
fix-devbox-up-old-tmp-socket

Conversation

@PabloZaiden
Copy link
Copy Markdown
Owner

This pull request introduces logic to ensure compatibility for SSH agent socket mounts in managed containers, particularly when the host's SSH agent socket path changes. The main addition is a utility that checks and, if necessary, creates or updates a symlink so the container's SSH mount always points to the current SSH agent socket. This helps prevent issues when the SSH agent socket moves between devcontainer sessions. Comprehensive tests have also been added to verify this behavior.

SSH Agent Socket Mount Compatibility:

  • Added ensureManagedContainerSshMountCompatibility in src/runtime.ts to check if the SSH agent socket mount in a managed container is up-to-date, and to create or update a symlink if needed. This function handles cases where the mount source is missing, stale, or already correct, and returns a status string describing the action taken.
  • Updated src/cli.ts to call this new function before starting the devcontainer, and to log messages when a symlink is created or updated. [1] [2]

Supporting Utilities:

  • Added helpers in src/runtime.ts for resolving bind mount sources and handling missing paths, used by the compatibility logic.
  • Extended imports in both src/runtime.ts and tests/runtime.test.ts to include necessary filesystem methods for symlink management and inspection. [1] [2]

Testing:

  • Added a new test suite in tests/runtime.test.ts to verify all code paths of ensureManagedContainerSshMountCompatibility, including creation, update, no-op, and not-applicable scenarios. [1] [2]

Types:

  • Introduced the ManagedContainerSshMountCompatibility type to clearly describe the possible outcomes of the compatibility check.

Copilot AI review requested due to automatic review settings April 15, 2026 12:26
@PabloZaiden PabloZaiden enabled auto-merge (squash) April 15, 2026 12:26
@PabloZaiden PabloZaiden merged commit 22a35e9 into main Apr 15, 2026
3 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses SSH agent socket mount breakages in managed devcontainers when the host SSH_AUTH_SOCK path changes between sessions by introducing host-side compatibility symlink management and wiring it into the up flow.

Changes:

  • Added ensureManagedContainerSshMountCompatibility to detect stale/missing bind-mount sources and create/update a symlink to the current SSH_AUTH_SOCK.
  • Invoked the compatibility check in the CLI up flow and logged when a symlink is created/updated.
  • Added tests covering key outcomes (created-symlink, updated-symlink, unchanged, not-applicable).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/runtime.ts Implements SSH mount compatibility logic (bind-mount source detection + symlink creation/update) and introduces outcome type.
src/cli.ts Calls the compatibility function before devcontainer up and prints user-facing messages for symlink actions.
tests/runtime.test.ts Adds a test suite validating the new compatibility behavior across multiple scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/runtime.ts
Comment on lines +632 to +660
const resolvedMountedSource = path.resolve(mountedSource);
const resolvedCurrentSshAuthSock = path.resolve(trimmedSshAuthSockSource);
if (resolvedMountedSource === resolvedCurrentSshAuthSock) {
return "unchanged";
}

let mountedSourceStat: Awaited<ReturnType<typeof lstat>> | null = null;
try {
mountedSourceStat = await lstat(resolvedMountedSource);
} catch (error) {
if (!isMissingPathError(error)) {
throw error;
}
}

if (!mountedSourceStat) {
await mkdir(path.dirname(resolvedMountedSource), { recursive: true });
await symlink(resolvedCurrentSshAuthSock, resolvedMountedSource);
return "created-symlink";
}

if (!mountedSourceStat.isSymbolicLink()) {
return "unchanged";
}

const existingTarget = await readlink(resolvedMountedSource);
const resolvedExistingTarget = path.resolve(path.dirname(resolvedMountedSource), existingTarget);
if (resolvedExistingTarget === resolvedCurrentSshAuthSock) {
return "unchanged";
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

Path comparisons here use path.resolve, which does not canonicalize symlinks. On macOS (e.g. /tmp vs /private/tmp) or when SSH_AUTH_SOCK itself is a symlink, this can cause an unnecessary updated-symlink outcome even though both paths refer to the same underlying socket. Consider comparing canonicalized paths (e.g. via the existing resolveComparablePath helper or realpath where available) before deciding to update the symlink.

Copilot uses AI. Check for mistakes.
Comment thread src/runtime.ts
return "unchanged";
}

await rm(resolvedMountedSource);
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

rm(resolvedMountedSource) can throw ENOENT if the symlink is removed between the earlier lstat/readlink and this call (or if the path is concurrently modified). Using rm(..., { force: true }) (consistent with other rm usages in the repo) or unlink with a missing-path guard would make this update step more robust.

Suggested change
await rm(resolvedMountedSource);
await rm(resolvedMountedSource, { force: true });

Copilot uses AI. Check for mistakes.
Comment thread tests/runtime.test.ts
),
).resolves.toBe("unchanged");
});

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The new suite doesn’t currently cover the branch where the existing mount source is a symlink that already points at the current SSH_AUTH_SOCK (the isSymbolicLink() + resolvedExistingTarget === resolvedCurrentSshAuthSock path). Adding a test for that scenario would help ensure the function doesn’t rewrite a correct symlink and keeps the outcome as unchanged.

Suggested change
test("does nothing when the existing mount source is a symlink that already resolves to the current host ssh agent socket", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "devbox-test-"));
tempPaths.push(tempDir);
const currentSocketPath = path.join(tempDir, "current", "agent.sock");
const compatibilitySocketPath = path.join(tempDir, "compat", "agent.sock");
await mkdir(path.dirname(currentSocketPath), { recursive: true });
await mkdir(path.dirname(compatibilitySocketPath), { recursive: true });
await writeFile(currentSocketPath, "current");
await symlink(currentSocketPath, compatibilitySocketPath);
await expect(
ensureManagedContainerSshMountCompatibility(
{
Id: "container-1",
Mounts: [
{
Type: "bind",
Source: compatibilitySocketPath,
Destination: "/run/devbox-ssh-auth.sock",
},
],
},
currentSocketPath,
),
).resolves.toBe("unchanged");
expect(await readlink(compatibilitySocketPath)).toBe(currentSocketPath);
});

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants