Repository navigation
Conversation
## Summary Completes the resolution of coder#207 by adding remote machine settings support to the `vscode-desktop` module: - Add `settings` map variable to configure machine-level settings before first user connection - Add `settings_file` variable pointing to `~/.vscode-server/data/Machine/settings.json` by default - Add finite, login-blocking `apply_settings` coder_script executing during workspace startup - Safely merge configured settings with existing settings (supporting comments and trailing commas) - Atomically update symlinks without destroying user symlink targets - Document settings usage and bump version examples to v1.4.0 - Add tests in `main.tftest.hcl` and `main.test.ts`
|
/review |
DevelopmentCats
left a comment
There was a problem hiding this comment.
Full review with the context of registry/coder/modules/vscode-desktop. I verified behavior rather than only reading it: rendered scripts/apply-settings.sh.tftpl and executed it end to end (create, merge into JSONC, symlink write-through, dangling symlink, malformed settings), ran scripts/merge-settings.py against comments, block comments, and trailing commas, ran shellcheck (severity=warning) on the template, and probed the Terraform typing edge cases in terraform console.
What works well
- Injection safety:
settingsandsettings_fileare base64 encoded before being embedded, so user input cannot break out of the shell quoting. - Atomicity:
mktempin the target directory plusmvgives a same-filesystem atomic replace, and the original file is left untouched when parsing or merging fails (verified). - The JSONC stripper handles strings, escaped quotes, BOMs (
utf-8-sig), and unterminated block comments correctly. - Deep merge preserves the key order of existing settings and appends new keys (verified).
- Zero overhead default: no script resource is created when
settingsis empty (covered in both test suites). - Version bump 1.3.0 -> 1.4.0 (minor for a feature) matches the AGENTS.md convention, and README examples are updated everywhere.
Blocking
1. CI is red: Prettier fails on two files
The "Check for typos and unformatted code" check fails on registry/coder/modules/vscode-desktop/main.test.ts (stray blank line at EOF) and registry/coder/modules/vscode-desktop/README.md (alignment of the settings example). Please run bun run fmt and push.
2. README claims comments are preserved, they are not
registry/coder/modules/vscode-desktop/README.md line 84 says existing settings "including JSONC comments and trailing commas" are preserved. They are accepted, but comments are stripped and the file is rewritten as plain JSON (verified: a file with // and /* */ comments and a trailing comma comes back without them). Real user impact: someone whose settings.json is managed in a dotfiles repo silently loses their comments on first start. See the inline suggestion to reword; optionally log a notice when comments were stripped.
Should fix
3. Dangling symlink blocks login for the full timeout
registry/coder/modules/vscode-desktop/scripts/resolve-settings-path.py line 6 uses resolve(strict=True). If settings_file is a symlink whose target does not exist yet, this raises, the script exits 1, and because start_blocks_login = true the user is blocked from logging in for the full 300s on every start. With strict=False a dangling symlink resolves to its target path, the existing [ ! -e ] branch then creates the file, and the symlink stays intact.
4. settings accepts non-map values, the failure only surfaces at runtime
registry/coder/modules/vscode-desktop/main.tf types settings as any with no validation. I confirmed in terraform console that a string passes local.has_settings ("foo" != {} is true), so a mistaken settings = "editor.fontSize" reaches the runtime: jsonencode produces a JSON string and the script either writes an invalid settings.json (new file, verified: exits 0 with a bare JSON string on disk) or crashes in merge() with AttributeError (existing file), blocking login for the timeout. A validation block catches this at plan time. I also checked that map(any) is not an option here: Terraform rejects the README's mixed number/bool example with "all map elements must have the same type", so validation is the right tool.
5. Empty settings.json hard-fails
A 0-byte settings.json raises JSONDecodeError and fails the startup script (verified), blocking login for 300s. Treating an empty file as {} is a cheap safeguard.
6. No automated test exercises the merge or symlink path
The trickiest logic (JSONC stripping, merge, symlink write-through) ships without runtime coverage, while the extensions flow has container tests in main.test.ts. Suggest a container test that writes a JSONC settings file plus a symlink, runs the rendered script, and asserts the merged output and intact symlink. Happy to sketch one if useful.
Nits
apply-settings.sh.tftplforceschmod 600on merge, silently overwriting existing permissions (e.g. 644). Consider preserving the original mode (stat -c %a) or documenting the change.- The
trapis registered after bothmktempcalls, so if the secondmktempfails the first temp file leaks. Register the trap right after the firstmktemp. expand_homeonly handles$HOME, not~/. Asettings_fileof"~/x"creates a literal~directory relative to the agent cwd. Worth a README note or tilde support.- On first creation the file is written as compact single-line JSON (
jsonencode), on merge it is pretty printed withindent=2. Consider pretty printing both for consistency. timeout = 300is generous for a purely local merge with no network. 60s would still be plenty and shrinks the worst-case login block when the script fails (e.g. missingpython3). Also consider documenting in the README that this script blocks login on start, like the extensions section does.
Questions
- AGENTS.md asks for
coder-utilsorchestration when reworking module scripts (ordering, materialization under$HOME/.coder-modules/coder/vscode-desktop/, logs). This PR adds a hand-rolledcoder_scriptwith inline Python and no on-disk logs. Deliberate exception for consistency withvscode-desktop-core? If it stays, consider writing a log line under the module logs dir. - Path assumption worth confirming: extensions use a custom
extensions_dir($HOME/.vscode-server/extensions), while settings target the default~/.vscode-server/data/Machine/settings.json. Please confirm the server launched by the Coder remote extension reads machine settings from that default data dir.
Residual risk: I could not run the full bun check suite here (the TS tests require Docker and the container tooling is unavailable in my review environment), so CI remains the source of truth for those. Everything else above was verified directly.
| } | ||
| ``` | ||
|
|
||
| Existing settings in `settings.json` (including JSONC comments and trailing commas) are preserved, and user symlinks are respected and atomically updated. |
There was a problem hiding this comment.
Verified: comments are not preserved. The pipeline strips comments and trailing commas and rewrites the file with json.dump, so the output is plain JSON, and users lose comments they may have in a dotfiles-managed settings.json. Suggested reword so the docs match behavior:
| Existing settings in `settings.json` (including JSONC comments and trailing commas) are preserved, and user symlinks are respected and atomically updated. | |
| Existing keys and values in `settings.json` are preserved and merged on startup. The file is rewritten as plain JSON, so JSONC comments and trailing commas are accepted but not kept. Symlinks are resolved and their targets are updated atomically. |
| ); | ||
| }); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Trailing blank line at EOF, Prettier flags this file (CI check failure). Empty suggestion removes the line:
|
|
||
|
|
||
| try: | ||
| print(Path(sys.argv[1]).resolve(strict=True)) |
There was a problem hiding this comment.
A dangling symlink (target not created yet) makes resolve(strict=True) raise OSError, the script exits 1, and with start_blocks_login = true the user is blocked from logging in for the full 300s on every start. With strict=False the symlink resolves to its target path, the existing [ ! -e ] branch creates the file, and the symlink stays intact.
| print(Path(sys.argv[1]).resolve(strict=True)) | |
| print(Path(sys.argv[1]).resolve(strict=False)) |
| variable "settings" { | ||
| type = any | ||
| description = "A map of settings to apply to VS Code Desktop's Machine settings. These settings are merged with any existing machine settings on startup." | ||
| default = {} | ||
| } |
There was a problem hiding this comment.
settings has no plan-time guard. A string value passes local.has_settings (verified: "foo" != {} is true in Terraform) and then either writes an invalid settings file or crashes merge-settings.py at runtime, blocking login. This validation rejects non-maps at plan time:
| variable "settings" { | |
| type = any | |
| description = "A map of settings to apply to VS Code Desktop's Machine settings. These settings are merged with any existing machine settings on startup." | |
| default = {} | |
| } | |
| variable "settings" { | |
| description = "A map of settings to apply to VS Code Desktop's Machine settings. These settings are merged with any existing machine settings on startup." | |
| type = any | |
| default = {} | |
| validation { | |
| condition = var.settings == null ? true : can([for key, value in var.settings : value]) | |
| error_message = "settings must be a map of setting names to values." | |
| } | |
| } |
Note map(any) is not a substitute: Terraform rejects mixed-type maps like the README example with "all map elements must have the same type".
| source = source_file.read() | ||
| return json.loads(strip_trailing_commas(strip_jsonc_comments(source))) |
There was a problem hiding this comment.
A 0-byte settings.json currently raises JSONDecodeError and fails the startup script (verified), blocking login for 300s. Treating an empty file as an empty object is a cheap safeguard:
| source = source_file.read() | |
| return json.loads(strip_trailing_commas(strip_jsonc_comments(source))) | |
| source = source_file.read() | |
| if not source.strip(): | |
| return {} | |
| return json.loads(strip_trailing_commas(strip_jsonc_comments(source))) |
Closes #207
Description
Completes the resolution of #207 by adding remote machine settings pre-configuration support to the
vscode-desktopmodule.While PR #1094 introduced the
extensionspre-installation capability, issue #207 also requires pre-applying VS Code machine settings before the user's first connection to the workspace. This PR implements that capability natively in thevscode-desktopmodule.Type of Change
Module Information
Path:
registry/coder/modules/vscode-desktopNew version:
v1.4.0Breaking change: [ ] Yes [x] No
What Changed
Variables Added
settingsany{}settings_filestring"$HOME/.vscode-server/data/Machine/settings.json"Implementation Details
settingsis configured, acoder_scriptresource (apply_settings) runs on workspace start (start_blocks_login = true, timeout 300s).settingsis empty or unset, no additional script resource is created (count = 0).README.md.main.tftest.hclandmain.test.ts.Usage Example
Testing & Validation
main.tftest.hclcovering default (no script) and configured (script created, blocks login)main.test.tsmerge-settings.pyagainst valid JSON, JSONC with comments, trailing commas, and deep object mergingextensions,folder, andopen_recentbehaviors unchanged