Skip to content

fix: resolve debug console file links to remote filesystem in devcontainers - #307244

Open
Yogeshwaran C (yogeshwaran-c) wants to merge 2 commits into
microsoft:mainfrom
yogeshwaran-c:fix/debug-console-file-links-remote
Open

fix: resolve debug console file links to remote filesystem in devcontainers#307244
Yogeshwaran C (yogeshwaran-c) wants to merge 2 commits into
microsoft:mainfrom
yogeshwaran-c:fix/debug-console-file-links-remote

Conversation

@yogeshwaran-c

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Bug fix

What is the current behavior?

When connected to a remote environment (e.g., devcontainer), clicking a file:///path/to/file link or an absolute path link in the Debug Console opens the file from the host filesystem instead of the remote filesystem. The terminal handles this correctly, but the debug console does not.

Closes #111143

What is the new behavior?

When environmentService.remoteAuthority is set (remote/devcontainer context), the debug console link detector now:

  1. For file: URI web links (createWebLink): Constructs a vscode-remote URI using the remote authority and checks if the file exists on the remote filesystem first. If found, opens from remote. Falls back to local filesystem if not found.

  2. For absolute path links (createPathLink): Same approach — tries the remote filesystem first via vscode-remote scheme, falls back to local on failure.

This matches how the terminal already resolves file links in remote environments, as suggested in the maintainer comment.

Additional context

  • Only one file changed: src/vs/workbench/contrib/debug/browser/linkDetector.ts
  • Uses the existing Schemas.vscodeRemote and IWorkbenchEnvironmentService.remoteAuthority which are already imported/injected
  • The URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: uri.path }) pattern follows established codebase conventions (e.g., promptFilesLocator.ts)
  • Preserves full backward compatibility — when not connected to a remote, behavior is unchanged
  • No new dependencies or imports required

…ontainers

When connected to a remote (e.g., devcontainer), file: URIs and absolute
path links in the debug console now check the remote filesystem first
before falling back to the local filesystem. Previously, clicking a
file:///path/to/file link in the debug console always opened it from
the host filesystem, even when the file was on the remote.

This aligns the debug console behavior with how the terminal already
handles file links in remote environments.

Closes microsoft#111143

Copilot AI 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.

Pull request overview

Fixes Debug Console link resolution in remote/devcontainer windows by preferring vscode-remote resources when remoteAuthority is present, aligning behavior more closely with Terminal link handling (issue #111143).

Changes:

  • Update file: web-link handling to attempt opening the corresponding vscode-remote URI first, with local fallback.
  • Update absolute path link handling to stat/resolve remote paths first, with local fallback.
  • Refactor selection computation to avoid duplication when opening editors.
Comments suppressed due to low confidence (1)

src/vs/workbench/contrib/debug/browser/linkDetector.ts:333

  • In the remote branch, if stat(remoteUri) succeeds but the result is a directory, the code returns without decorating the link and without attempting the local uri fallback. This can cause a valid local file path to stop being linkified when a remote directory happens to exist at the same path. Consider treating isDirectory the same as “not found” and falling back to the local stat(uri) path.
			this.fileService.stat(remoteUri).then(stat => {
				if (stat.isDirectory) {
					return;
				}
				this.decorateLink(link, remoteUri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: remoteUri, options: { ...options, preserveFocus } }));
			}).catch(() => {
				// Remote file not found, fall back to local filesystem
				this.fileService.stat(uri).then(stat => {
					if (stat.isDirectory) {
						return;
					}
					this.decorateLink(link, uri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));

Comment on lines +252 to +263
const remoteAuthority = this.environmentService.remoteAuthority;
if (remoteAuthority) {
const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: uri.path });
const existsRemote = await this.fileService.exists(remoteUri);
if (existsRemote) {
await this.editorService.openEditor({
resource: remoteUri,
options: { pinned: true, selection },
});
return;
}
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

fileService.exists(remoteUri) can still reject if withProvider(remoteUri) throws (e.g. scheme provider not available or transient remote disconnect). Since this is awaited without a try/catch, the click handler will throw and skip the local fallback, even when the local file exists. Consider wrapping the remote check in try/catch and falling back to the local fileUri on any error.

This issue also appears on line 322 of the same file.

Copilot uses AI. Check for mistakes.
Comment on lines +316 to +347
// When connected to a remote, try the remote filesystem first
// since the debug adapter likely produces paths from the remote host
// https://github.com/microsoft/vscode/issues/111143
const remoteAuthority = this.environmentService.remoteAuthority;
if (remoteAuthority) {
const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: uri.path });
this.fileService.stat(remoteUri).then(stat => {
if (stat.isDirectory) {
return;
}
this.decorateLink(link, remoteUri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: remoteUri, options: { ...options, preserveFocus } }));
}).catch(() => {
// Remote file not found, fall back to local filesystem
this.fileService.stat(uri).then(stat => {
if (stat.isDirectory) {
return;
}
this.decorateLink(link, uri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
}).catch(() => {
// If the uri can not be resolved we should not spam the console with error, remain quiet #86587
});
});
} else {
this.fileService.stat(uri).then(stat => {
if (stat.isDirectory) {
return;
}
this.decorateLink(link, uri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
}).catch(() => {
// If the uri can not be resolved we should not spam the console with error, remain quiet #86587
});
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

createPathLink performs fileService.stat(remoteUri) during linkification (render time) for every detected path when in a remote window, potentially doubling filesystem round-trips due to the local fallback stat(uri). In output-heavy debug sessions this can add noticeable latency. Consider deferring remote/local existence checks until click time (similar to createWebLink), or caching results to avoid repeated remote stat calls.

Suggested change
// When connected to a remote, try the remote filesystem first
// since the debug adapter likely produces paths from the remote host
// https://github.com/microsoft/vscode/issues/111143
const remoteAuthority = this.environmentService.remoteAuthority;
if (remoteAuthority) {
const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: uri.path });
this.fileService.stat(remoteUri).then(stat => {
if (stat.isDirectory) {
return;
}
this.decorateLink(link, remoteUri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: remoteUri, options: { ...options, preserveFocus } }));
}).catch(() => {
// Remote file not found, fall back to local filesystem
this.fileService.stat(uri).then(stat => {
if (stat.isDirectory) {
return;
}
this.decorateLink(link, uri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
}).catch(() => {
// If the uri can not be resolved we should not spam the console with error, remain quiet #86587
});
});
} else {
this.fileService.stat(uri).then(stat => {
if (stat.isDirectory) {
return;
}
this.decorateLink(link, uri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
}).catch(() => {
// If the uri can not be resolved we should not spam the console with error, remain quiet #86587
});
}
// When connected to a remote, prefer the remote filesystem first
// since the debug adapter likely produces paths from the remote host
// https://github.com/microsoft/vscode/issues/111143
const remoteAuthority = this.environmentService.remoteAuthority;
const remoteUri = remoteAuthority ? URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: uri.path }) : undefined;
this.decorateLink(
link,
remoteUri ?? uri,
fulltext,
hoverBehavior,
async (preserveFocus: boolean) => {
const editorOptions = { ...options, preserveFocus };
if (remoteUri) {
try {
const stat = await this.fileService.stat(remoteUri);
if (!stat.isDirectory) {
await this.editorService.openEditor({ resource: remoteUri, options: editorOptions });
return;
}
} catch {
// Remote file not found or not accessible, fall back to local filesystem
}
}
try {
const stat = await this.fileService.stat(uri);
if (!stat.isDirectory) {
await this.editorService.openEditor({ resource: uri, options: editorOptions });
}
} catch {
// If the uri cannot be resolved we should not spam the console with error, remain quiet #86587
}
}
);

Copilot uses AI. Check for mistakes.
Comment on lines +249 to +263
// When connected to a remote, try the remote filesystem first
// since the debug adapter likely produces paths from the remote host
// https://github.com/microsoft/vscode/issues/111143
const remoteAuthority = this.environmentService.remoteAuthority;
if (remoteAuthority) {
const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: uri.path });
const existsRemote = await this.fileService.exists(remoteUri);
if (existsRemote) {
await this.editorService.openEditor({
resource: remoteUri,
options: { pinned: true, selection },
});
return;
}
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

New remote-resolution behavior (preferring vscode-remote resources and falling back to local) isn’t covered by the existing Debug Link Detector tests. Adding unit tests that stub environmentService.remoteAuthority, fileService.exists/stat, and editorService.openEditor would help prevent regressions (remote hit, remote miss -> local fallback, and error cases).

Copilot uses AI. Check for mistakes.
@roblourens

Copy link
Copy Markdown
Member

This looks generally good but please take a look at Copilot's code review comments

@yogeshwaran-c

Copy link
Copy Markdown
Contributor Author

Addressed Copilot's code review feedback in d17b12b:

  1. try/catch around fileService.exists(remoteUri) in createWebLink — Previously, if the remote scheme provider was unavailable (e.g. transient disconnect), the exists() call could reject and skip the local filesystem fallback entirely. Now wrapped in try/catch so the click handler gracefully falls through to local resolution.

  2. Deferred remote stat in createPathLink to click time — Previously, fileService.stat(remoteUri) ran at linkification (render) time for every detected path in a remote window, doubling filesystem round-trips. Now the remote-vs-local resolution is deferred into the click handler callback via decorateLink(..., async (preserveFocus) => { ... }), eliminating render-time I/O for the remote case. The non-remote (local-only) path remains unchanged with its existing render-time stat approach.

  3. Unit tests — Adding dedicated unit tests for the new remote-resolution behavior is deferred for a follow-up. The runtime behavior is now safe against provider failures in both code paths.

- Wrap `fileService.exists(remoteUri)` in try/catch in `createWebLink`
  so that a transient remote disconnect or missing scheme provider falls
  through to the local filesystem instead of throwing in the click handler.

- Defer remote `fileService.stat()` in `createPathLink` to click time
  instead of render time, avoiding doubled filesystem round-trips during
  linkification for every detected path in remote windows.
@yogeshwaran-c
Yogeshwaran C (yogeshwaran-c) force-pushed the fix/debug-console-file-links-remote branch from d17b12b to f8551e7 Compare April 6, 2026 16:33
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.

Ctrl+click on file: link in Debug Console in devcontainer opens host filesystem instead

3 participants