@@ -265,29 +265,64 @@ export class Remote {
265
265
agent = matchingAgents [ 0 ]
266
266
}
267
267
268
- let remotePlatforms = this . vscodeProposed . workspace
268
+ const hostname = authorityParts [ 1 ]
269
+ const remotePlatforms = this . vscodeProposed . workspace
269
270
. 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" )
275
275
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.
276
281
let settingsContent = "{}"
277
282
try {
278
283
settingsContent = await fs . readFile ( this . storage . getUserSettingsPath ( ) , "utf8" )
279
284
} catch ( ex ) {
280
285
// Ignore! It's probably because the file doesn't exist.
281
286
}
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
+ }
291
326
}
292
327
293
328
const workspaceUpdate = new vscode . EventEmitter < Workspace > ( )
@@ -431,6 +466,23 @@ export class Remote {
431
466
await this . updateSSHConfig ( authorityParts [ 1 ] , hasCoderLogs )
432
467
433
468
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
+
434
486
if ( ! pid ) {
435
487
// TODO: Show an error here!
436
488
return
0 commit comments