@@ -265,29 +265,64 @@ export class Remote {
265265 agent = matchingAgents [ 0 ]
266266 }
267267
268- let remotePlatforms = this . vscodeProposed . workspace
268+ const hostname = authorityParts [ 1 ]
269+ const remotePlatforms = this . vscodeProposed . workspace
269270 . getConfiguration ( )
270- . get < Record < string , string > > ( "remote.SSH.remotePlatform" )
271- remotePlatforms = {
272- ...remotePlatforms ,
273- [ `${ authorityParts [ 1 ] } ` ] : agent . operating_system ,
274- }
271+ . get < Record < string , string > > ( "remote.SSH.remotePlatform" , { } )
272+ const connTimeout = this . vscodeProposed . workspace
273+ . getConfiguration ( )
274+ . get < number | undefined > ( "remote.SSH.connectTimeout" )
275275
276+ // We have to directly munge the settings file with jsonc because trying to
277+ // update properly through the extension API hangs indefinitely. Possibly
278+ // VS Code is trying to update configuration on the remote, which cannot
279+ // connect until we finish here leading to a deadlock. We need to update it
280+ // locally, anyway, and it does not seem possible to force that via API.
276281 let settingsContent = "{}"
277282 try {
278283 settingsContent = await fs . readFile ( this . storage . getUserSettingsPath ( ) , "utf8" )
279284 } catch ( ex ) {
280285 // Ignore! It's probably because the file doesn't exist.
281286 }
282- const parsed = jsonc . parse ( settingsContent )
283- parsed [ "remote.SSH.remotePlatform" ] = remotePlatforms
284- const edits = jsonc . modify ( settingsContent , [ "remote.SSH.remotePlatform" ] , remotePlatforms , { } )
285- try {
286- await fs . writeFile ( this . storage . getUserSettingsPath ( ) , jsonc . applyEdits ( settingsContent , edits ) )
287- } catch ( ex ) {
288- // The user will just be prompted instead, which is fine!
289- // If a user's settings.json is read-only, then we can't write to it.
290- // This is the case when using home-manager on NixOS.
287+
288+ // Add the remote platform for this host to bypass a step where VS Code asks
289+ // the user for the platform.
290+ let mungedPlatforms = false
291+ if ( ! remotePlatforms [ hostname ] || remotePlatforms [ hostname ] !== agent . operating_system ) {
292+ remotePlatforms [ hostname ] = agent . operating_system
293+ settingsContent = jsonc . applyEdits (
294+ settingsContent ,
295+ jsonc . modify ( settingsContent , [ "remote.SSH.remotePlatform" ] , remotePlatforms , { } ) ,
296+ )
297+ mungedPlatforms = true
298+ }
299+
300+ // VS Code ignores the connect timeout in the SSH config and uses a default
301+ // of 15 seconds, which can be too short in the case where we wait for
302+ // startup scripts. For now we hardcode a longer value. Because this is
303+ // potentially overwriting user configuration, it feels a bit sketchy. If
304+ // microsoft/vscode-remote-release#8519 is resolved we can remove this but
305+ // for now to mitigate the sketchiness we will reset it after connecting.
306+ const minConnTimeout = 1800
307+ let mungedConnTimeout = false
308+ if ( ! connTimeout || connTimeout < minConnTimeout ) {
309+ settingsContent = jsonc . applyEdits (
310+ settingsContent ,
311+ jsonc . modify ( settingsContent , [ "remote.SSH.connectTimeout" ] , minConnTimeout , { } ) ,
312+ )
313+ mungedConnTimeout = true
314+ }
315+
316+ if ( mungedPlatforms || mungedConnTimeout ) {
317+ try {
318+ await fs . writeFile ( this . storage . getUserSettingsPath ( ) , settingsContent )
319+ } catch ( ex ) {
320+ // This could be because the user's settings.json is read-only. This is
321+ // the case when using home-manager on NixOS, for example. Failure to
322+ // write here is not necessarily catastrophic since the user will be
323+ // asked for the platform and the default timeout might be sufficient.
324+ mungedPlatforms = mungedConnTimeout = false
325+ }
291326 }
292327
293328 const workspaceUpdate = new vscode . EventEmitter < Workspace > ( )
@@ -431,6 +466,23 @@ export class Remote {
431466 await this . updateSSHConfig ( authorityParts [ 1 ] , hasCoderLogs )
432467
433468 this . findSSHProcessID ( ) . then ( ( pid ) => {
469+ // Once the SSH process has spawned we can reset the timeout.
470+ if ( mungedConnTimeout ) {
471+ // Re-read settings in case they changed.
472+ fs . readFile ( this . storage . getUserSettingsPath ( ) , "utf8" ) . then ( async ( rawSettings ) => {
473+ try {
474+ await fs . writeFile (
475+ this . storage . getUserSettingsPath ( ) ,
476+ jsonc . applyEdits ( rawSettings , jsonc . modify ( rawSettings , [ "remote.SSH.connectTimeout" ] , connTimeout , { } ) ) ,
477+ )
478+ } catch ( error ) {
479+ this . storage . writeToCoderOutputChannel (
480+ `Failed to reset remote.SSH.connectTimeout back to ${ connTimeout } : ${ error } ` ,
481+ )
482+ }
483+ } )
484+ }
485+
434486 if ( ! pid ) {
435487 // TODO: Show an error here!
436488 return
0 commit comments