After Developer: Reload Window in Positron, the ggsql console session does not come back. R and Python sessions in the same window reconnect normally.
The cause is the extension identity that Positron records for the ggsql runtime. The extension gets the Positron API through a global accessor, and Positron cannot attribute that call to an extension. Positron therefore records the runtime under nullExtensionDescription. The reconnect path uses this identity to activate the owning extension, so the reconnect fails.
Steps to reproduce
- Open a workspace in Positron.
- Start a ggsql console session.
- Run Developer: Reload Window.
- Look at the Console pane.
Expected: the ggsql session reconnects, in the same way as R and Python.
Actual: the ggsql session does not come back.
Evidence
Positron stores the affiliated runtime metadata for each workspace in state.vscdb, under ~/Library/Application Support/Positron/User/workspaceStorage/<workspace-hash>/ on macOS. The ggsql entry names no extension:
// positron.affiliatedRuntimeMetadata.v2.ggsql
{
"extensionId": { "value": "nullExtensionDescription", "_lower": "nullextensiondescription" },
"runtimeId": "ggsql-1c7963a6d5c5",
"runtimeName": "ggsql (Jupyter)",
"runtimeVersion": "0.4.1",
"languageId": "ggsql",
"startupBehavior": "explicit",
"sessionLocation": "workspace"
}
The R entry in the same workspace names the correct extension:
// positron.affiliatedRuntimeMetadata.v2.r
{
"extensionId": { "value": "positron.positron-r", "_lower": "positron.positron-r" },
"runtimeId": "003663c34c4e2f88a5145a4c13757481",
"runtimeName": "R 4.6.0",
"languageId": "r",
"startupBehavior": "implicit",
"sessionLocation": "workspace",
"cacheable": true
}
Root cause
src/extension.ts calls tryAcquirePositronApi() from @posit-dev/positron. That helper calls globalThis.acquirePositronApi(). Positron defines this global in src/bootstrap-esm.ts as createRequire(import.meta.url) and then require('positron').
The require parent is therefore the Positron bootstrap file, not the ggsql extension. The require interceptor in src/vs/workbench/api/common/extHostRequireInterceptor.ts looks up the parent path in its extension map and finds nothing. It logs Could not identify extension for 'positron' require call from ... and returns an API object bound to nullExtensionDescription. Every runtime that GgsqlRuntimeManager yields carries that identity.
R and Python use a plain import * as positron from 'positron' inside their own source files, which the interceptor attributes correctly.
Why this breaks the reload path
restoreWorkspaceSessions in src/vs/workbench/services/runtimeStartup/common/runtimeStartup.ts activates the owning extension before it reconnects:
await this.activateExtension(
session.runtimeMetadata.extensionId,
session.runtimeMetadata.languageId);
For ggsql this argument is nullExtensionDescription, so activateById fails. The error is caught and logged at debug level, and the ggsql extension is never activated on this path.
Validation and restore then call runtimeManagerForRuntime(metadata, wait: true) in src/vs/workbench/api/common/positron/extHostLanguageRuntime.ts. This function waits 10 seconds for a runtime manager that only registers when onStartupFinished fires. doRestoreRuntimeSession in src/vs/workbench/services/runtimeSession/common/runtimeSession.ts also throws at once when no session manager is registered. The session is dropped.
Ruled out
- The kernel survives the reload. The supervisor only tears down sessions on desktop Quit, not on Reload (
extensions/positron-supervisor/src/extension.ts). A live ggsql-jupyter process and its kcserver are both present after a reload.
sessionLocation: 'workspace' and startupBehavior: 'explicit' are both correct. R and Python also use workspace by default, and only manual blocks affiliated startup.
runtimeId is stable across reloads, because it is a hash of the kernel path.
Proposed fix
Get the Positron API inside a source file of the ggsql extension, so the require interceptor can attribute the call:
let positronApi: typeof import('@posit-dev/positron') | undefined;
try {
positronApi = require('positron');
} catch {
// Running in VS Code, where the module does not exist.
}
Add 'positron' to the external array in esbuild.js, which today lists only 'vscode'.
Other gaps found during the investigation
package.json declares no extensionDependencies. Both positron-r and positron-python declare positron.positron-supervisor. ggsql activates the supervisor ad hoc inside createSession and restoreSession. We can't do this, because the extension has to work in VS Code as well.
restoreSession in src/manager.ts awaits supervisorApi.restoreSession(...) before it returns. RSession returns at once and defers the supervisor call to start(). Any transient supervisor error currently rejects the whole restore.
restoreSession discards the sessionName argument that Positron passes, and hardcodes sessionName: 'ggsql'. A renamed session loses its name after a reload.
- The runtime metadata does not set
cacheable: true, so ggsql is excluded from the runtime discovery cache that R and Python use for warm starts.
GgsqlRuntimeManager._sessions is written but never read.
After Developer: Reload Window in Positron, the ggsql console session does not come back. R and Python sessions in the same window reconnect normally.
The cause is the extension identity that Positron records for the ggsql runtime. The extension gets the Positron API through a global accessor, and Positron cannot attribute that call to an extension. Positron therefore records the runtime under
nullExtensionDescription. The reconnect path uses this identity to activate the owning extension, so the reconnect fails.Steps to reproduce
Expected: the ggsql session reconnects, in the same way as R and Python.
Actual: the ggsql session does not come back.
Evidence
Positron stores the affiliated runtime metadata for each workspace in
state.vscdb, under~/Library/Application Support/Positron/User/workspaceStorage/<workspace-hash>/on macOS. The ggsql entry names no extension:The R entry in the same workspace names the correct extension:
Root cause
src/extension.tscallstryAcquirePositronApi()from@posit-dev/positron. That helper callsglobalThis.acquirePositronApi(). Positron defines this global insrc/bootstrap-esm.tsascreateRequire(import.meta.url)and thenrequire('positron').The require parent is therefore the Positron bootstrap file, not the ggsql extension. The require interceptor in
src/vs/workbench/api/common/extHostRequireInterceptor.tslooks up the parent path in its extension map and finds nothing. It logsCould not identify extension for 'positron' require call from ...and returns an API object bound tonullExtensionDescription. Every runtime thatGgsqlRuntimeManageryields carries that identity.R and Python use a plain
import * as positron from 'positron'inside their own source files, which the interceptor attributes correctly.Why this breaks the reload path
restoreWorkspaceSessionsinsrc/vs/workbench/services/runtimeStartup/common/runtimeStartup.tsactivates the owning extension before it reconnects:For ggsql this argument is
nullExtensionDescription, soactivateByIdfails. The error is caught and logged at debug level, and the ggsql extension is never activated on this path.Validation and restore then call
runtimeManagerForRuntime(metadata, wait: true)insrc/vs/workbench/api/common/positron/extHostLanguageRuntime.ts. This function waits 10 seconds for a runtime manager that only registers whenonStartupFinishedfires.doRestoreRuntimeSessioninsrc/vs/workbench/services/runtimeSession/common/runtimeSession.tsalso throws at once when no session manager is registered. The session is dropped.Ruled out
extensions/positron-supervisor/src/extension.ts). A liveggsql-jupyterprocess and itskcserverare both present after a reload.sessionLocation: 'workspace'andstartupBehavior: 'explicit'are both correct. R and Python also useworkspaceby default, and onlymanualblocks affiliated startup.runtimeIdis stable across reloads, because it is a hash of the kernel path.Proposed fix
Get the Positron API inside a source file of the ggsql extension, so the require interceptor can attribute the call:
Add
'positron'to theexternalarray inesbuild.js, which today lists only'vscode'.Other gaps found during the investigation
We can't do this, because the extension has to work in VS Code as well.package.jsondeclares noextensionDependencies. Bothpositron-randpositron-pythondeclarepositron.positron-supervisor. ggsql activates the supervisor ad hoc insidecreateSessionandrestoreSession.restoreSessioninsrc/manager.tsawaitssupervisorApi.restoreSession(...)before it returns.RSessionreturns at once and defers the supervisor call tostart(). Any transient supervisor error currently rejects the whole restore.restoreSessiondiscards thesessionNameargument that Positron passes, and hardcodessessionName: 'ggsql'. A renamed session loses its name after a reload.cacheable: true, so ggsql is excluded from the runtime discovery cache that R and Python use for warm starts.GgsqlRuntimeManager._sessionsis written but never read.