Conversation
Task 1/6: null-destructure fix AC: #1 Story: config-hardening
- Add platformToolsDir override to gimlet.json, resolved via
globalState.getPlatformToolsDir(); all three hardcoded Solana paths
(LLDB library, LLDB Python site-packages, LLDB scripts dir) now
derive from it. Users who install platform-tools outside
~/.cache/solana/v{version}/ — renamed dirs, Nix, custom toolchains —
can point Gimlet at their install with one key.
- Add lldbLibraryPath as a finer-grained override that wins over the
derived LLDB default; useful when the library has a non-conventional
filename or the liblldb.{ext} symlink is missing.
- Move lldbLibrary to a lazy getter on globalState; require() no
longer throws when platform-tools is missing, letting the
litesvm/mollusk gate run cleanly. invalidateLldbLibrary() is called
on override or version changes.
- debugConfigManager consumes globalState.getPlatformToolsLibDir/BinDir
for its scripts dir and Python path; the "not found" toast names
platformToolsDir as an alternative remediation.
- Relocate updates construction in runDebugSessionIteration into the
existing try/catch so the lazy getter's first-read throw surfaces as
a Gimlet toast instead of an unhandled exception in the polling loop.
- withLldbConfig and its inject/restore contract are unchanged.
Task 2/6: Changes 1+2+2a (amendment A-1)
AC: #2, #3, #3b, #4, #4b, #5, #11
Story: config-hardening
… toast
Add inline SCHEMA + validateConfig() in globalState.js. setConfig now
runs validation first, applies only keys that pass, and returns
{ errors, unknownKeys } to the caller. Validation checks type,
optional numeric range, and optional regex pattern.
config.js surfaces validation via a single aggregated error toast
listing every offending key with expected-vs-actual type, while
preserving the current in-memory value for each bad key (activation
proceeds with defaults). Unknown keys (typos, user comments-as-keys)
are logged to the Gimlet Output channel instead of rejected.
The success "Gimlet config updated and state refreshed." info toast
is suppressed when errors exist, so the user sees one message, not two.
Task 3/6: Change 4
AC: #6
Story: config-hardening
Add depsPath override to gimlet.json. Resolution tiers:
1. gimlet.json.depsPath (workspace-relative, containment-checked,
rejects paths that escape the workspace root — same rule as
sbfTraceDir)
2. $CARGO_TARGET_DIR/deploy/debug (as-is; may live outside the
workspace since env vars often do)
3. workspace/target/deploy/debug default
Drop inputPath entirely. It was vestigial: resolveGimletConfig()
computed and returned it, but no caller destructured or consumed it.
Rather than make a dead key configurable, remove the field, the
resolution block, and the returned-object entry. See amendment A-3.
Harden scanDeployDirectory against a .so-less depsPath. A directory
that exists and lives inside the workspace but contains no compiled
programs directly (e.g. user sets "./target" instead of
"./target/deploy/debug") used to silently pass with an empty
executables map, surfacing as cryptic "Unknown program ID" errors
later. Now counts .so files seen and surfaces a single actionable
toast naming the resolved path and the common mistake. See A-4.
SCHEMA + setConfig extended with depsPath; README documents the new
key; quick-spec updated with AC-7 and AC-7b.
Task 4/6: Change 3 (amendments A-3, A-4)
AC: #7, #7b, #8
Story: config-hardening
ensureGimletConfig previously rewrote gimlet.json on every activation
even when the merged content was byte-for-byte identical to what was
already on disk. Combined with the Cargo.toml watcher that re-runs
activation on every save, this produced:
- continuous mtime changes (noisy git status)
- "file changed externally" prompts mid-edit
- self-write feedback risk with the config watcher — which becomes
more pronounced once Task 6 lands onDidCreate/onDidDelete coverage
Serialize once into `next`, read current file into `prev` (or null),
only fs.writeFileSync when they differ.
One-time spurious write possible on first run if the file's previous
encoding differs in whitespace — acceptable and self-stabilising.
Task 5/6: Change 6
AC: #9
Story: config-hardening
activateDebugger re-runs on Cargo.toml saves and manual triggers; each call previously registered a fresh FileSystemWatcher without disposing the prior one, so one edit to gimlet.json fired N reload toasts. Store the watcher on the manager and dispose it before re-registering. Also route onDidCreate and onDidDelete through the same handlers: atomic-save editors fire delete+create instead of change, and file deletion now resets all overrides to null with a clear toast. AC-10, AC-10b. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tasks 1-5 of config-hardening committed code without running the formatter; running `npm run format` normalized the full tree for the first time. No logic changes — quote-style normalization, trailing commas, arrow-function parens, and line wrapping only. Verified with `git diff -w` spot-checks across .vscode/launch.json, src/state/globalState.js, and src/managers/portManager.js. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores .github/* to origin/main to keep PR #57 focused. Files contained only quote-style and indentation reformatting, no semantic changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores README.md to origin/main formatting. Re-applies only the content additions for this PR: 2 new bullets describing the custom platform-tools install / LLDB library overrides, 3 new gimlet.json options rows (depsPath, platformToolsDir, lldbLibraryPath), and a "Which key do I need?" tip paragraph. No reformatting of existing lines. Diff vs main is content-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores jsconfig.json to origin/main. The branch version had only cosmetic edits — tab→4-space indent, comma repositioned around a comment, single-element arrays collapsed to one line — none of which affect TypeScript/JSDoc behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores eslint.config.mjs to origin/main. Branch version had only cosmetic edits — quote style, array/object expansion, indent depth, trailing newline — no rule changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores files to the state at 42daf29 (the commit just before the prettier sweep in a3075a5) — or to origin/main when prettier was the only thing that touched them. Keeps every feature/fix commit's logic; removes the cosmetic noise that was making review hard. Files identical to main after revert (drop from PR entirely): - .gitignore - src/debug.js - src/logger.js - src/projectStructure.js - src/state/sessionState.js - src/utils.js - src/managers/vscodeSettingsManager.js Files restored to 42daf29 (content kept, formatting reverted): - docs/amendments.md - docs/quick-config-hardening.md - src/extension.js - src/lens/gimletCodeLensProvider.js - src/managers/portManager.js - src/managers/sessionManager.js - src/state/globalState.js Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lost in the bulk prettier-revert of 13f1135 — .claude was a real content addition on this branch (ignore Claude Code local state), not formatting churn. Bringing it back. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These docs do not belong in the PR.
Replace startsWith(workspaceFolder + path.sep) with isInsideWorkspace helper backed by path.relative. The old check was case-sensitive and separator-literal, which mis-rejected valid paths on Windows/macOS when casing differed and could not reliably detect different-drive paths on Windows. The new helper honors the platform's filesystem rules (case-insensitive on Windows/macOS) and rejects '..' escapes plus absolute (different-drive) results.
Wrap the watcher target in vscode.RelativePattern instead of passing the raw absolute path string. This matches the documented form for workspace-scoped watchers and makes the intent explicit (anchor + relative glob) without relying on VS Code's string-as-glob inference.
setConfig({}) now restores tcpPort, stopOnEntry, and platformToolsVersion
to their defaults instead of leaving them at their last value. This makes
the watcher's resetToDefaults handler — invoked when gimlet.json is
deleted — actually match its toast ("using defaults"). Previously, the
path-style overrides cleared via `|| null` but the scalars only assigned
when the key was present, so a deleted config left them stuck.
Also extracts DEFAULT_STOP_ON_ENTRY constant for symmetry with the other
defaults.
There was a problem hiding this comment.
Pull request overview
This PR makes Gimlet’s debugger/session behavior configurable via a workspace .vscode/gimlet.json, including validation, persistence into globalState, and live reloads via a file watcher.
Changes:
- Add schema-based validation and default-reset behavior for Gimlet config values, with lazy LLDB library resolution.
- Add
.vscode/gimlet.jsoncreation + file watching to reload config on save/delete, plus workspace-containment checks for certain paths. - Update debugger launch plumbing and UX messages (e.g., improved LLDB/platform-tools diagnostics; better deploy-dir scanning errors).
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/state/globalState.js | Adds schema validation, new override fields, and lazy/cached LLDB library path resolution. |
| src/config.js | Creates/merges gimlet.json, validates + applies config into globalState, adds watcher + containment checks. |
| src/managers/portManager.js | Moves LLDB config resolution inside try/catch so config errors are surfaced cleanly. |
| src/managers/debugConfigManager.js | Derives platform-tools paths from globalState overrides; updates missing-tools error messaging. |
| src/extension.js | Makes deploy-dir scanning resilient to null config; improves error when no .so files are present. |
| README.md | Documents new configuration options and guidance. |
| .gitignore | Ignores .claude. |
Comments suppressed due to low confidence (1)
src/config.js:124
- ensureGimletConfig() only auto-writes
tcpPort,platformToolsVersion, andstopOnEntry, but the PR description says gimlet.json is auto-created with the current values for additional configurable keys (depsPath, sbfTraceDir, platformToolsDir, lldbLibraryPath). If the intent is discoverability via the generated file, include these keys (likely asnullwhen unset) so users can see the full set of supported overrides.
const defaultConfig = {
tcpPort: globalState.tcpPort,
platformToolsVersion: globalState.platformToolsVersion,
stopOnEntry: globalState.stopOnEntry,
};
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The Default column listed `null` for keys whose descriptions documented concrete fallbacks, contradicting the prose. Replace those with the actual derived defaults (workspace-relative `$PWD/...` paths, the platform-tools cache location, and the derived liblldb path) so the table matches the behavior the descriptions describe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/config.js:124
ensureGimletConfig()only writestcpPort,platformToolsVersion, andstopOnEntryinto the auto-created.vscode/gimlet.json. That means users won’t see (or be able to easily discover) the newly supported options likeartifactPath,sbfTracePath,platformToolsPath, andlldbLibraryPathin the generated file, despite the PR description/README advertising them. Consider including all supported keys indefaultConfig(with workspace-relative defaults ornullwhere appropriate).
const defaultConfig = {
tcpPort: globalState.tcpPort,
platformToolsVersion: globalState.platformToolsVersion,
stopOnEntry: globalState.stopOnEntry,
};
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
procdump
left a comment
There was a problem hiding this comment.
Suggestions from a separate code review pass. Most are message-wording / cleanup; the inline ones are anchored on diff lines. Two more sit on lines that aren't part of this PR's diff, so I'm leaving them here as text — feel free to take or leave any of them.
src/extension.js:142 — await the config init so internal throws don't become unhandled rejections.
ensureGimletConfig() is async and contains synchronous fs calls (fs.mkdirSync, fs.writeFileSync) that can throw on permission errors. Without await, any throw becomes an unhandled promise rejection — the user sees nothing. Adding await routes errors through activateDebugger's existing outer try/catch.
- gimletConfigManager.ensureGimletConfig();
+ await gimletConfigManager.ensureGimletConfig();
gimletConfigManager.watchGimletConfig(context);src/managers/debugConfigManager.js:103 — replace silent abort with a thrown error.
if (!session) return false; is the only remaining false-return path in loadProgramModules. portManager catches a thrown error and shows a toast, but a false return triggers stopDebugging() with no explanation. The race is rare (session cleanup vs. debug start) but when it happens the debug session vanishes mysteriously.
- if (!session) return false;
+ if (!session) {
+ throw new Error(
+ 'Gimlet debug session was cleared before program modules could load. ' +
+ 'This usually means the session was stopped concurrently — try debugging again.'
+ );
+ }If you take this, loadProgramModules no longer needs return true at the bottom, and the matching if (!loaded) branch in portManager.js:143-146 becomes dead code — both can go.
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| @@ -53,30 +155,58 @@ class GimletGeneralState { | |||
| setPlatformToolsVersion(version) { | |||
There was a problem hiding this comment.
nit: dead code - remove
Summary
Makes Gimlet configurable via
.vscode/gimlet.json. Users can now override:tcpPort— debug session port (default1212)stopOnEntry— pause at program entry (defaulttrue)platformToolsVersion— Solana platform-tools version (default1.54)platformToolsDir— full override of the platform-tools rootlldbLibraryPath— direct path toliblldb.{dylib,so}for non-standard layoutsdepsPath— where compiled programs live (defaulttarget/deploy/debug)sbfTraceDir— where SBF traces are written (defaulttarget/sbf/trace)gimlet.jsonis auto-created on activation with current values and re-read on save. Invalid keys are reported in a single aggregated toast; unknown keys are logged and ignored. Deleting the file resets all overrides to defaults. LLDB library resolution is lazy and re-runs on relevant config changes.Changes
.vscode/gimlet.jsonwith schema-validated keys and a workspace-relative file watcherglobalStateis now the single source of truth for config; defaults restored when keys are absentdepsPath,sbfTraceDir)Test plan
.vscode/gimlet.jsonand confirm the toast + state updategimlet.jsonand confirm "using defaults" toast + behavior revertsplatformToolsVersionto a missing version and confirm the LLDB error toastdepsPath/sbfTraceDiroutside the workspace and confirm rejection