fix: prompt for update parameters before CLI update#945
Conversation
558f101 to
436f566
Compare
436f566 to
cdc26fe
Compare
|
/coder-agents-review |
cdc26fe to
b59cadd
Compare
There was a problem hiding this comment.
First-pass review (Netero only). One P0: the new test file has a broken import that prevents the entire updateWorkspace/startWorkspace test suite from loading. The 424 new test lines are dead code until this is fixed.
The panel has not yet reviewed this PR. Fixing this P0 first avoids spending parallel review time on code that may shift once tests actually run.
Netero's take: "The test file is a ghost town. 462 lines of coverage, zero executions. One import stands between this PR and meaningful test feedback."
1 P0, 0 lower.
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
Clean feature design. The parameter collection flow, state machine fallback with upfront mode downgrade, and workspace propagation through getWorkspace() are well-structured. The test infrastructure (controlSpawn, quickInputMock, table-driven parameter tests) is solid for async CLI + UI interactions. DEREM-1 (broken import) fixed in b59cadd.
One P1 blocks merge: escapeCommandArg only escapes " inside double quotes, but spawn uses shell: true. Template parameter names and option values come from the server with no character restriction, so a malicious template admin can achieve code execution on the user's local machine. Six of fourteen reviewers independently flagged this. Fix: either single-quote the parameter arg (matching getHeaderArgs in settings/headers.ts), or refactor runCliCommand to use spawn(binary, argsArray) without shell: true.
The PR description explains what the PR does but not why the CLI's own prompts can't be used. The doc comment on updateWorkspace has the answer ("the resolver phase can't render an interactive terminal"), but a reviewer reading the description first would wonder why coder update can't prompt the user directly.
Pariston: "The fix doesn't create a problem worse than the original."
1 P1, 6 P3, 4 Nit.
src/api/workspace.ts:131
Nit [DEREM-14] "canceled" vs "cancelled" inconsistency. Codebase convention for user-facing messages is "cancelled" (17 instances across progress.ts, workspaceStateMachine.ts, cliManager.ts, remote.ts). This line uses American spelling "canceled" to mirror the server API status string, but it's a user-facing error message, not an API enum. (Leorio)
🤖
src/api/workspace.ts:275
P3 [DEREM-3] makeValidator has 6 branching paths and formatConstraint has 6 paths, none with direct test coverage. Server-side validation is the safety net, so incorrect client validation produces confusing inline messages but does not pass bad values to the CLI. The functions total ~35 lines of branching logic. (Netero)
🤖
🤖 This review was automatically generated with Coder Agents.
|
Addressing the two notes embedded in the review body (DEREM-3 and DEREM-14): DEREM-3 (validator/constraint coverage): added a DEREM-14 ( All other inline threads replied to individually. |
|
/coder-agents-review |
892c77c to
ecac995
Compare
There was a problem hiding this comment.
R2 findings thoroughly addressed. 12 of 13 open findings fixed in 892c77c with corresponding tests. The escapeShellArg extraction and deduplication with getHeaderArgs is a trust signal: the author generalized from a pointed correction. The WorkspaceUpdateCancelledError class cleanly separates user cancellation from failures. DEREM-7 (empty required field) contested; panel closed 6/6, accepting the author's defense that the stepped quick-input flow provides sufficient context.
Two new issues surfaced during fix verification. The DEREM-4 fix is correct on Unix (single quotes block all expansion) but incomplete on Windows: cmd.exe does not treat \" as an escape, and &, |, <, >, ^ are metacharacters even inside double quotes (DEREM-17, P1). Separately, the parameter collection filter is narrower than the CLI's prompting conditions; three scenarios exist where the CLI reaches an interactive prompt the extension didn't pre-collect, causing an unrecoverable hang because stdin is never closed (DEREM-18, P2).
Hisoka on the running-case status guard: "Dormant today because coder update blocks until the workspace is running. Breaks when CLI semantics change."
1 P1, 1 P2, 5 P3.
🤖 This review was automatically generated with Coder Agents.
3652d57 to
900ebc9
Compare
|
/coder-agents-review |
There was a problem hiding this comment.
R3 findings comprehensively addressed. The structural decision to drop shell: true from runCliCommand (DEREM-17) eliminates the entire class of shell injection bugs. proc.stdin.end() (DEREM-18) converts future unmatched CLI prompts into catchable errors. Dropping client-side regex (DEREM-22) removes the ReDoS surface. The ${env:VAR} substitution is a clean replacement for the lost shell expansion. All R3 fixes verified by Netero and the panel.
Two P2 findings from the shell: true removal. First, spawn without a shell changes the failure channel for a missing binary: Node emits an error event instead of a shell-reported non-zero exit. There is no error handler, so the promise never settles and the progress dialog hangs (DEREM-23, 3 reviewers). One-line fix: proc.on("error", reject). Second, getGlobalFlags includes getHeaderArgs output which applies escapeShellArg. Without a shell, those baked-in quotes become literal characters, breaking any multi-word --header-command value for update and start operations (DEREM-24).
Hisoka on the spawn error: "The DEREM-17 fix correctly removed shell: true but changed the failure channel. With a shell, a missing binary produces stderr and a non-zero close code. Without a shell, Node emits an error event on the ChildProcess."
2 P2, 2 P3.
src/api/workspace.ts:83
P3 [DEREM-25] Close handler drops the signal parameter, producing "exited with code null" when the process is killed.
proc.on("close", (code: number) => { ... })annotatescodeasnumberand ignores the second parameter. Node's signature is(code: number | null, signal: NodeJS.Signals | null). When killed by signal (OOM, SIGTERM), code is null. An operator sees"exited with code null"with no indication of what killed the process.
Fix: accept (code: number | null, signal: NodeJS.Signals | null) and include signal in the message when code is null.
(Chopper P3, Hisoka P3)
🤖
🤖 This review was automatically generated with Coder Agents.
|
Addressing DEREM-25 from the R4 review body (no inline thread for it): Fixed in 83c36a3. The
Stderr is still appended when available. Added a regression test ( All other R4 inline threads replied to individually. |
83c36a3 to
3b17d24
Compare
`coder start` and `coder update` can need to prompt for template parameters, but the VS Code workbench's terminal renderer isn't initialized during `resolveAuthority` — so a PTY terminal created in that phase silently drops all output and can't be used to surface prompts. See microsoft/vscode#12000 and #144729: extension-Pseudoterminal writes before `open()` fires are ignored, and `open()` doesn't fire until the workbench has mounted the terminal panel at least once. Run the CLI via plain `spawn` again and stream output through the existing write callback (output channel). Before invoking `coder update`, fetch the new template version's rich parameters and the workspace's current build parameters; for any required parameter that has no default and no existing value, prompt the user with `vscode.window.showInputBox` (with regex/range validation) or `showQuickPick` (when the parameter declares options). Pass the collected values as `--parameter name=value` flags so the CLI never has to prompt. Also propagate the post-update workspace through the state machine so the agent check uses the fresh build's resources.
- Move parameter prompts and validation to src/api/updateParameters.ts.
- Escape --parameter values via escapeShellArg so server-controlled names
and values cannot inject shell metacharacters under shell: true.
- Clear stale agent id after a stopped/failed to running update.
- Treat JSON null on validation_min/max as unset.
- Substitute {min}/{max}/{value} in validation_error.
- Drop the warning toast when the user cancels a parameter prompt.
- Rename untilHidden to collectInput; canceled to cancelled.
- Modernize tests: Promise.withResolvers, vi.waitFor, multi-select coverage.
- Drop `shell: true` from `runCliCommand`; pass args as an array via
`getGlobalFlags` (raw). Eliminates Windows cmd.exe quoting concerns
entirely (DEREM-17) and lets `--parameter` values pass through verbatim.
- Close child stdin after spawn so unexpected interactive CLI prompts
see EOF and error out instead of hanging (DEREM-18).
- Guard the running case of the state machine against post-update builds
that aren't running yet (DEREM-19).
- Drop client-side regex validation (server validates with RE2, ReDoS-safe)
to prevent extension-host freezes on hostile patterns (DEREM-22).
- Expand `${env:VAR}` in `coder.globalFlags` so users can reference
environment variables now that there's no outer shell expansion.
- Document the new globalFlags expansion semantics in package.json.
- Handle spawn 'error' events (ENOENT, EACCES) in runCliCommand so missing or unexecutable binaries reject the promise instead of hanging the progress dialog (DEREM-23). - Thread the shell-escape choice through to getHeaderArgs so the value is only quoted in shell:true contexts. Multi-word coder.headerCommand values now reach the CLI verbatim in execFile/no-shell paths (DEREM-24). - Report the terminating signal in the error message when the process is killed (DEREM-25). - Expose stdinEnd from the spawn test harness and assert it in the happy-path test so the DEREM-18 EOF fix is locked in (DEREM-26). Also added regression tests for spawn 'error' and signal-kill paths, and updated cliExec/cliConfig tests for the corrected no-shell header value.
3b17d24 to
e647f3b
Compare
Splits the shell-handling change out of this PR so it can land (and be
reverted) on its own. Reinstates:
- `runCliCommand` spawns through `shell: true` again, with the workspace
identifier wrapped in `escapeShellArg` (so server-controlled values
still flow through the cmd.exe-safe `""`/`%%` escape introduced in R2).
- `coder update` parameter values go through `escapeShellArg` in
updateParameters.ts before being passed as `--parameter name=value`.
- `getUserGlobalFlags` returns `coder.globalFlags` verbatim again; the
`${env:VAR}` substitution was tied to the no-shell path and moves to
the follow-up PR.
- package.json `coder.globalFlags` description drops the env-expansion
note for now.
Kept (orthogonal hardening, no shell dependence):
- `proc.stdin.end()` to EOF unexpected CLI prompts (DEREM-18).
- State-machine running-case guard (DEREM-19).
- Server-side regex only, no client-side regex eval (DEREM-22).
- `getHeaderArgs(esc)` still threads the escape choice so execFile
callers (speedtest/supportBundle) don't get quoted header values
(DEREM-24).
- Signal-aware close handler reporting `signal SIGTERM` etc. (DEREM-25).
- `stdinEnd` assertion in the happy-path test (DEREM-26).
Dropped the `proc.on("error", reject)` handler since with `shell: true`
a missing binary surfaces as a non-zero close from the shell, not a
spawn error event; the corresponding ENOENT test is removed.
Restores the test-side `shellQuote` mirror of `escapeShellArg` in
test/utils/platform.ts (and its tests) for asserting shell-escaped
values cross-platform.
3dc671f to
dfa3c13
Compare
Summary
InputBox/QuickPickprompts before runningcoder update.spawn(binary, args, {}), so values reachcoderexactly as typed and there's no quote-escaping surface on Windows. The REST API fallback is preserved for older CLIs.Fixes #939.
Notes for reviewers
Parameter prompting + validation lives in a new
src/api/updateParameters.tsmodule sosrc/api/workspace.tsstays focused on CLI orchestration. User cancellation throws a dedicatedWorkspaceUpdateCancelledErrorso the state machine can skip the failure warning toast and log it as info instead.Hardening picked up during review:
runCliCommandno longer spawns through a shell, removing the Windowscmd.exeinjection surface and the need for cross-platform escape rules.proc.stdin.end()is called immediately after spawn so any future CLI prompt the extension didn't pre-collect EOFs and errors out instead of hanging.${env:VAR}references incoder.globalFlagsare now substituted (replacement for the lost shell expansion); documented inpackage.json.Tests
test/unit/api/updateParameters.test.tscovering parameter collection, prompt validation, cancellation, multi-select serialization, and a ReDoS regression guard.escapeShellArgcovered intest/unit/util.test.tsfor both platforms.${env:VAR}substitution covered intest/unit/cliConfig.test.ts.maybeUpdate.Supporting context
The original issue was caused by
coder updateprompting for parameter values while the extension streams CLI output to a read-only VS CodeOutputChannel.This PR keeps the CLI update path but avoids interactive terminal prompts during remote setup by pre-collecting only parameters that need new values:
The collected values are passed as
--parameter name=valueargv entries tocoder update. If the CLI is older than--parameter-awarecoder update, the existing REST API fallback handles the version bump.