Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@
a kernel installed on the machine, and a fixed path in `ggsql.kernelPath`;
configuring `ggsql.kernelPath` alone continues to mean that path is used.

- The extension now runs the bundled kernel before offering it, and falls back to
a kernel installed on the machine when it does not start. A bundled binary can
be built against newer system libraries than the host provides — every
filesystem check passes and the kernel still dies the moment it is launched —
so the extension runs `ggsql-jupyter --version` once per update and treats a
failure as "not usable here". When nothing on the machine can run, including
builds that carry no kernel at all, the extension says so once and points at
the install instructions instead of offering a runtime that cannot start.

- `ggsql-jupyter` accepts `--version`.

### Changed
- Dodging now only takes effect where groups actually meet on a position. A
layer whose grouping gives every group a position of its own — `colour` mapped
Expand Down
3 changes: 3 additions & 0 deletions ggsql-jupyter/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ use std::process::Command;
#[derive(Parser)]
#[command(name = "ggsql-jupyter")]
#[command(about = "Jupyter kernel for ggsql", long_about = None)]
// `--version` doubles as a liveness probe: the VS Code extension runs it to
// confirm a kernel binary actually loads on this machine before offering it.
#[command(version)]
struct Args {
/// Path to the Jupyter connection file
#[arg(short = 'f', long = "connection-file")]
Expand Down
4 changes: 4 additions & 0 deletions ggsql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
uses one installed on the machine.
- Fixed: no ggsql runtime is offered when no kernel can be found, rather than one
that fails at session start with `KS-19: Kernel path not found`.
- The bundled kernel is run before it is offered, and a kernel installed on the
machine is used instead when it does not start. If nothing on the machine can
run, the extension points at the install instructions once rather than offering
a runtime that fails at session start.

## 0.3.2

Expand Down
11 changes: 7 additions & 4 deletions ggsql-vscode/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,25 @@ The extension ships the kernel: the per-platform VSIXes carry `ggsql-jupyter` at

| Strategy | Candidates, in priority order |
| --- | --- |
| `bundled` (default) | The bundled kernel alone. A build that carries none falls through to the host locations, so the platform-neutral VSIX behaves as it always did. |
| `bundled` (default) | The bundled kernel, with host locations behind it as a fallback tier reached only when it cannot run. A build that carries no kernel goes straight to the host locations. |
| `environment` | Host locations, then the bundled kernel as the fallback. |
| `path` | `ggsql.kernelPath` alone — neither the bundled kernel nor a host install stands in for it. An empty path is treated as `bundled`. |

Host locations are, in order: Jupyter kernelspec directories (user then system), the native package install locations per platform, then `PATH`.

`selectKernelCandidates()` is the whole precedence rule with no filesystem in it, which is what `src/test/kernelDiscovery.test.ts` exercises; `discoverKernelPaths()` supplies it with what is actually on disk.
`selectKernelCandidates()` is the whole precedence rule with no filesystem in it, which is what `src/test/kernelDiscovery.test.ts` exercises; `discoverKernelPaths()` supplies it with what is actually on disk. It returns a `KernelSelection`: the `candidates` to offer, plus a `fallback()` callback for the tier behind them. The fallback is a callback rather than a list because reaching the host locations shells out to `which`/`where`, and the common case — a bundled kernel that runs — must not pay for it.

Four things here are load bearing:
Five things here are load bearing:

- **A kernel is run before it is offered.** Filesystem checks cannot tell whether a binary starts. The bundled kernel is built for the platform but not for every system it can be installed on: one linked against newer shared libraries than the host provides is exec'd successfully and then killed by the dynamic linker, which no `stat` or `access` call can see. `probeKernel()` runs `ggsql-jupyter --version` and requires exit 0; only the bundled kernel is probed, since a kernel the user installed is their own business. A success is cached in `globalState` against the extension version, so it costs one spawn per update rather than one per window; a failure is not cached, because it is cheap to repeat and a host that gains the missing libraries should start working without waiting for an update.
- **Every candidate is an absolute path.** A candidate that is only a binary name satisfies each existence check further down and so registers a runtime that fails at session start with `KS-19: Kernel path not found`. `findOnPath()` returns `undefined` rather than the bare name, and `isKernelAccessible()` rejects any non-absolute path, so no kernel anywhere means **zero** runtimes rather than an unusable one. The single exception is a `ggsql.kernelPath` that resolves to nothing: it is passed through so discovery can report it as inaccessible in the log instead of ignoring the setting silently.
- **The bundled kernel's `runtimeId` is fixed, not derived from its path.** Every other source hashes `kernelPath` to get one id per installed kernel, but the bundled path contains the versioned extension directory, so hashing it would mint a new runtime on every extension update and lose the workspace's runtime affinity and its restorable sessions.
- **The bundled runtime is named plain `ggsql`.** The `ggsql (<source>)` suffix is only worth showing for a kernel the user went out of their way to select.
- **`ggsql.kernelPath` implies `path`.** Users configured that setting before a strategy existed, so a non-empty path with no explicitly set `kernelStrategy` still resolves to `path`. `resolveKernelStrategy()` reads the value through `inspect()` for that reason: `get()` cannot tell a set value from the default.

Discovery also writes the user-level Jupyter kernelspec for the bundled and system kernels, so Quarto and Jupyter can find ggsql without a session ever being started, and so the spec stops pointing into an extension directory an update has removed.
Discovery also writes the user-level Jupyter kernelspec for the bundled and system kernels, so Quarto and Jupyter can find ggsql without a session ever being started, and so the spec stops pointing into an extension directory an update has removed. Only a kernel that has passed the probe is written there: the spec outlives the window and is what Quarto resolves, and it has no fallback of its own.

A fallback that succeeds is deliberately silent — the runtime's name in the picker already says where it came from, and the log records the handover. The one case that interrupts the user is the dead end: nothing runnable anywhere, whether because the bundled kernel failed its probe or because the build carries none (`win32-arm64` and the platform-neutral VSIX). `reportNoUsableKernel()` then shows a non-modal warning once per extension version, offering the install docs and the log. It is skipped under the `path` strategy, where the user named a binary and the log already reports it.

## Settings

Expand Down
5 changes: 5 additions & 0 deletions ggsql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export function log(message: string): void {
outputChannel.appendLine(`[${new Date().toISOString()}] ${message}`);
}

/** Reveal the ggsql output channel, for notifications that offer it. */
export function showLog(): void {
outputChannel.show();
}

/**
* Activates the extension.
*
Expand Down
Loading