feat(workspace): support async, durable workspace up execution - #798
Conversation
✅ Deploy Preview for images-devsy-sh canceled.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
🚧 Files skipped from review as they are similar to previous changes (11)
📝 WalkthroughWalkthroughWorkspace provisioning now supports persistent detached tasks, structured lifecycle status, tunnel and client propagation, and desktop IPC task streaming. Task commands support listing, retrieval, logs, cancellation, and removal. Supporting changes add process handling and concurrent feature resolution. ChangesWorkspace task execution and status reporting
Structured status propagation
Desktop detached flow
Supporting execution changes
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Deploy Preview for devsydev canceled.
|
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
desktop/src/main/ipc.ts (1)
751-841: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winOld tunnel process isn't awaited before its map entry is overwritten — can leak an orphaned child.
existing.kill("SIGTERM")(Line 756) is fire-and-forget; execution continues immediately into the detached submit and eventuallytunnelProcesses.set(wsId, child)(Line 841) overwrites the map key before the old process is confirmed dead. If it hasn't exited yet, its handle is lost — no futurequiesceWorkspacecall can find/kill it.quiesceWorkspace(Lines 187-199) already implements the correct await-exit pattern for exactly this; this block should share it instead of duplicating a weaker copy that also omits the cli/pty cancellation. Consider extracting the tunnel-kill logic (and the active-task-cancel logic duplicated at Lines 759-765 vs. 179-186) into one helper both call.🔒 Proposed fix — await exit before proceeding
const existing = tunnelProcesses.get(wsId) if (existing) { - existing.kill("SIGTERM") tunnelProcesses.delete(wsId) + const existingExit = new Promise<void>((resolve) => { + if (existing.exitCode !== null || existing.signalCode !== null) { + resolve() + return + } + existing.once("close", () => resolve()) + }) + existing.kill("SIGTERM") + await existingExit }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/main/ipc.ts` around lines 751 - 841, Update the workspace startup cleanup around tunnelProcesses and activeUpTasks to reuse the existing quiesceWorkspace cleanup logic, or extract a shared helper used by both paths. Ensure the existing tunnel child is terminated and awaited before replacing its map entry, and preserve cancellation of any active CLI task, including the existing cli/pty cleanup behavior implemented by quiesceWorkspace.pkg/devcontainer/run.go (1)
180-188: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDrop the outer
PhaseReadyfailure reporting for inner dispatch failures.
dispatchByConfigKindroutes to paths that already report the actual failing phase (PhaseBuildingImage,PhaseStartingContainer,PhaseInjectingAgent,PhaseRunningLifecycleHook). This outerstatus.Fail(reporter, status.PhaseReady, err)re-emits those failures withStep: "ready", which mislabels build/startup/agent/lifecycle failures and duplicates the failure event.🐛 Possible fix: only report the phase name once, closest to the source, and drop this generic outer Fail
result, err := r.dispatchByConfigKind(ctx, substitutedConfig, params) if result != nil { result.RecoveryContainer = r.recovering } if err != nil { - status.Fail(reporter, status.PhaseReady, err) + // Assumes the inner pipeline already reported the granular failing + // phase via status.Fail; avoid re-labeling it as "ready" here. return result, err } status.Leave(reporter, status.PhaseReady, "")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/devcontainer/run.go` around lines 180 - 188, Remove the outer status.Fail call for errors returned by dispatchByConfigKind in the run flow, while preserving the error return and result recovery assignment. Let the dispatched paths report their specific phases—PhaseBuildingImage, PhaseStartingContainer, PhaseInjectingAgent, or PhaseRunningLifecycleHook—without emitting a duplicate PhaseReady failure.pkg/client/clientimplementation/workspace_client.go (1)
1148-1163: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winForward the local-up status reporter into
runTunnelServer.
pkg/client/clientimplementation/workspace_client.go:1158-1161appends a finalWithStatusReporter(status.NewLogReporter()), which overwrites any per-call status reporter becausetunnelserver.New()applies options in order and each option setss.statusReporter. This ignores the caller’s progress/reporter setup, including theUpCommandReporterwired incmd/workspace/up/agent.go; status events from the agent over RPC are logged and then discarded. Threadcmd.statusReporterorUpOptions.ReporterintoBuildAgentClientOptions.TunnelOptionsinstead of forcing a discard-only logger here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/client/clientimplementation/workspace_client.go` around lines 1148 - 1163, The runTunnelServer function currently overwrites caller-provided reporters with status.NewLogReporter. Remove that forced reporter and ensure the local-up status reporter, such as cmd.statusReporter or UpOptions.Reporter, is propagated through BuildAgentClientOptions.TunnelOptions before reaching RunUpServer.
🧹 Nitpick comments (1)
pkg/client/clientimplementation/workspace_client.go (1)
288-315: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueTask-store file I/O runs while holding
s.mfor the wholeStatus()call.
taskStatusOverride()/latestUpTask()perform directory listing + per-file reads whiles.m.Lock()is held for the entire method, extending lock contention beyond what's needed to safely reads.workspace.ID.♻️ Suggested: snapshot the workspace ID, then do file I/O outside the lock
func (s *workspaceClient) Status( ctx context.Context, opt client.StatusOptions, ) (client.Status, error) { s.m.Lock() - defer s.m.Unlock() - var ( result client.Status err error ) switch { case s.isMachineProvider() && len(s.config.Exec.Status) > 0: result, err = s.machineStatus(ctx, opt) case opt.ContainerStatus: result, err = s.getContainerStatus(ctx) default: result, err = s.workspaceFolderStatus() } + workspaceID := s.workspace.ID + s.m.Unlock() if err != nil || result != client.StatusNotFound { return result, err } - - if override, ok := s.taskStatusOverride(); ok { + if override, ok := s.taskStatusOverride(workspaceID); ok { return override, nil } return result, nil }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/client/clientimplementation/workspace_client.go` around lines 288 - 315, Update workspaceClient.Status to avoid holding s.m during task-store I/O: lock only long enough to snapshot the workspace ID needed by taskStatusOverride/latestUpTask, then unlock before performing status resolution and override checks. Ensure taskStatusOverride uses the snapshot rather than reading s.workspace.ID while the mutex is held, preserving existing status precedence and return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/workspace/task.go`:
- Around line 154-194: Validate that the parsed interval is strictly positive
immediately after time.ParseDuration in the command handler, before passing it
through followTask or creating a ticker. Return a descriptive error for zero or
negative durations, while preserving the existing parse error and normal
positive-interval behavior.
In `@cmd/workspace/up/status.go`:
- Around line 30-39: Update plainStatusReporter.Report to handle non-started
phase events in addition to failures and starts, emitting a completion/ready log
for Leave events including the final PhaseReady event. Mirror the completion
behavior used by logReporter while preserving the existing failure and
phase-start messages.
In `@cmd/workspace/up/up.go`:
- Around line 226-237: Update the task initialization error paths in the
workspace-up flow so both openTask and SetWorkspaceID failures are passed
through reportErr(err, emitJSON, out) before returning. Preserve the existing
failTask behavior for SetWorkspaceID while ensuring JSON mode emits an error
envelope for either failure.
In `@pkg/client/clientimplementation/daemonclient/up.go`:
- Around line 450-492: Update statusSniffingWriter.Close to run the buffered
trailing bytes through config.ParseStatusLine before forwarding them to next,
matching the complete-line handling in Write. Report and suppress a trailing
status envelope without a newline; forward non-status content unchanged, then
clear the buffer and preserve the existing error behavior.
In `@pkg/command/process_supported.go`:
- Around line 45-47: In pkg/command/process_supported.go lines 45-47, update the
process escalation flow around syscall.Kill to retain and validate a stable
process identity before sending SIGKILL, avoiding blind signaling of a reused
numeric PID. In pkg/command/process_test.go lines 33-42, replace the stale
real-PID scenario with an injected or stubbed signal operation that returns
syscall.ESRCH, and verify the existing handling for that result.
In `@pkg/compose/helper_test.go`:
- Around line 194-196: Update the Podman reachability probe in the test setup to
use exec.CommandContext with a short timeout, canceling the context after the
probe completes. Preserve the existing skip behavior when the timed command
fails or times out.
In `@pkg/devcontainer/status/status.go`:
- Around line 32-35: Update jsonStatusReporter.Report to serialize access to Out
with synchronization before encoding and writing each status event. Ensure
concurrent tunnel Status RPC reports produce complete, non-interleaved NDJSON
lines while preserving the existing reporting behavior.
In `@pkg/task/store.go`:
- Around line 67-89: Update Store.Delete to acquire and hold the same per-id
flock used by update and write for the entire status-check and file-removal
sequence, releasing it on every return path. Reuse the existing locking helper
and preserve the force/non-terminal validation and deletion behavior while
ensuring no concurrent atomic rename can recreate the task after removal.
In `@pkg/task/task.go`:
- Around line 87-93: Guard Task.Succeed, Task.Fail, and taskReporter.Report with
the same State.Terminal() check used by Cancel before mutating state. Preserve
the existing terminal state and result/error when the task is already canceled
or otherwise terminal; only apply success, failure, or phase/step updates for
non-terminal tasks.
---
Outside diff comments:
In `@desktop/src/main/ipc.ts`:
- Around line 751-841: Update the workspace startup cleanup around
tunnelProcesses and activeUpTasks to reuse the existing quiesceWorkspace cleanup
logic, or extract a shared helper used by both paths. Ensure the existing tunnel
child is terminated and awaited before replacing its map entry, and preserve
cancellation of any active CLI task, including the existing cli/pty cleanup
behavior implemented by quiesceWorkspace.
In `@pkg/client/clientimplementation/workspace_client.go`:
- Around line 1148-1163: The runTunnelServer function currently overwrites
caller-provided reporters with status.NewLogReporter. Remove that forced
reporter and ensure the local-up status reporter, such as cmd.statusReporter or
UpOptions.Reporter, is propagated through BuildAgentClientOptions.TunnelOptions
before reaching RunUpServer.
In `@pkg/devcontainer/run.go`:
- Around line 180-188: Remove the outer status.Fail call for errors returned by
dispatchByConfigKind in the run flow, while preserving the error return and
result recovery assignment. Let the dispatched paths report their specific
phases—PhaseBuildingImage, PhaseStartingContainer, PhaseInjectingAgent, or
PhaseRunningLifecycleHook—without emitting a duplicate PhaseReady failure.
---
Nitpick comments:
In `@pkg/client/clientimplementation/workspace_client.go`:
- Around line 288-315: Update workspaceClient.Status to avoid holding s.m during
task-store I/O: lock only long enough to snapshot the workspace ID needed by
taskStatusOverride/latestUpTask, then unlock before performing status resolution
and override checks. Ensure taskStatusOverride uses the snapshot rather than
reading s.workspace.ID while the mutex is held, preserving existing status
precedence and return behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a92c0000-ca8f-4986-be56-496cfd1301e6
⛔ Files ignored due to path filters (2)
pkg/agent/tunnel/tunnel.pb.gois excluded by!**/*.pb.gopkg/agent/tunnel/tunnel_grpc.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (44)
cmd/internal/agentworkspace/up.gocmd/internal/container_tunnel.gocmd/workspace/task.gocmd/workspace/task_test.gocmd/workspace/up/agent.gocmd/workspace/up/detach.gocmd/workspace/up/detach_test.gocmd/workspace/up/status.gocmd/workspace/up/up.gocmd/workspace/up/up_flags.gocmd/workspace/workspace.godesktop/e2e/fixtures/mock-devsy.cjsdesktop/src/main/ipc.tsdesktop/src/renderer/src/lib/ipc/events.tsdesktop/src/renderer/src/lib/types/index.tsdesktop/src/shared/cli-error.tspkg/agent/tunnel/tunnel.protopkg/agent/tunnelserver/options.gopkg/agent/tunnelserver/status_sender.gopkg/agent/tunnelserver/tunnelserver.gopkg/client/client.gopkg/client/clientimplementation/daemonclient/stop.gopkg/client/clientimplementation/daemonclient/up.gopkg/client/clientimplementation/daemonclient/up_test.gopkg/client/clientimplementation/workspace_client.gopkg/client/clientimplementation/workspace_client_status_test.gopkg/command/process_supported.gopkg/command/process_test.gopkg/compose/helper_test.gopkg/config/pathmanager.gopkg/devcontainer/build.gopkg/devcontainer/config/envelope.gopkg/devcontainer/feature/extend.gopkg/devcontainer/feature/lockfile.gopkg/devcontainer/run.gopkg/devcontainer/setup.gopkg/devcontainer/single.gopkg/devcontainer/status/log.gopkg/devcontainer/status/status.gopkg/devcontainer/status/status_test.gopkg/flags/names/names.gopkg/task/store.gopkg/task/task.gopkg/task/task_test.go
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
desktop/src/main/ipc.ts (1)
177-183: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winDo not discard task ownership when cancellation fails.
The mapping is removed before cancellation and errors are swallowed. If cancellation fails,
workspace_stop/workspace_deleteproceeds while the detacheduptask still runs, and no later action can retry cancelling it. Retain the entry until cancellation succeeds (or the task is confirmed terminal), and propagate unexpected cancellation failures.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/main/ipc.ts` around lines 177 - 183, The cancelActiveUp function currently deletes the active task mapping and suppresses cancellation errors before cancellation succeeds. Move activeUpTasks.delete(workspaceId) until after a successful cancellation or confirmed terminal task state, and let unexpected cli.run failures propagate so workspace_stop/workspace_delete cannot proceed while the task remains active.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@desktop/src/main/ipc.ts`:
- Around line 761-764: Update the workspace_up flow around cancelActiveUp and
the detached submission/follower registration logic to serialize cancel, submit,
and registration per wsId, preventing concurrent requests from overwriting one
another. In result, error, and exit cleanup callbacks, remove tunnelProcesses
entries only when their stored task or process identity matches the completing
session; preserve newer workspace_up sessions and their cancellability.
---
Outside diff comments:
In `@desktop/src/main/ipc.ts`:
- Around line 177-183: The cancelActiveUp function currently deletes the active
task mapping and suppresses cancellation errors before cancellation succeeds.
Move activeUpTasks.delete(workspaceId) until after a successful cancellation or
confirmed terminal task state, and let unexpected cli.run failures propagate so
workspace_stop/workspace_delete cannot proceed while the task remains active.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 818eaff1-c8a6-478d-8d2a-cbb6c1e08c52
📒 Files selected for processing (11)
cmd/workspace/task.gocmd/workspace/up/agent.gocmd/workspace/up/status.gocmd/workspace/up/up.godesktop/src/main/ipc.tspkg/client/clientimplementation/daemonclient/up.gopkg/client/clientimplementation/workspace_client.gopkg/compose/helper_test.gopkg/task/store.gopkg/task/task.gopkg/task/task_test.go
🚧 Files skipped from review as they are similar to previous changes (9)
- cmd/workspace/up/status.go
- pkg/compose/helper_test.go
- cmd/workspace/up/agent.go
- pkg/task/task.go
- pkg/client/clientimplementation/workspace_client.go
- pkg/task/store.go
- cmd/workspace/task.go
- pkg/client/clientimplementation/daemonclient/up.go
- cmd/workspace/up/up.go
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
pkg/client/clientimplementation/workspace_client.go (1)
336-380: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftReconcile abandoned tasks before reporting provisioning.
latestUpTaskmaps every persisted non-terminal task toStatusProvisioning. If the detached worker is killed after task creation but before it records a terminal result, later status checks can report provisioning indefinitely for a missing workspace. Add worker liveness/heartbeat expiry or startup reconciliation before applying this override.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/client/clientimplementation/workspace_client.go` around lines 336 - 380, Update latestUpTask and taskStatusOverride so persisted non-terminal up tasks are reconciled before returning StatusProvisioning. Detect abandoned tasks using the task worker’s existing liveness/heartbeat expiry or startup reconciliation mechanism, mark them terminal/failed as appropriate, and only report provisioning for an actively running task; preserve the existing best-effort ok=false behavior when task state cannot be read.desktop/src/main/ipc.ts (1)
239-242: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
quiesceWorkspacebypasses the serialization chain, so stop/delete can still race an in-flightupsubmission.
workspace_upnow runscancel → submit → registerinsideserializePerWorkspace, butquiesceWorkspace(used byworkspace_stop/workspace_delete) callscancelActiveUpdirectly. If a stop lands while anupis betweencli.run([... "--detach"])andactiveUpTasks.set(wsId, taskId), the cancel observes no mapping, then theupregisters its task id — the detached task survives the stop/delete and keeps running against a workspace that is being torn down.Enqueue the quiesce on the same per-workspace chain.
🔒️ Proposed fix
async function quiesceWorkspace(workspaceId: string): Promise<void> { - await cancelActiveUp(workspaceId) - await Promise.all([cli.cancelFor(workspaceId), pty.cancelFor(workspaceId)]) + await serializePerWorkspace(workspaceId, async () => { + await cancelActiveUp(workspaceId) + await Promise.all([ + cli.cancelFor(workspaceId), + pty.cancelFor(workspaceId), + ]) + }) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/main/ipc.ts` around lines 239 - 242, Update quiesceWorkspace to execute cancelActiveUp and the cli/pty cancellation operations through serializePerWorkspace for the given workspaceId, ensuring it joins the same chain used by workspace_up. Preserve the existing cancellation ordering and Promise.all behavior while preventing stop/delete from racing an in-flight submission.pkg/agent/tunnelserver/options.go (1)
80-83: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winPreserve the no-op reporter when the option receives nil.
Line 82 can overwrite the
status.Nop()default with nil. A laterstatus.Enter/Failcall then invokesReporton a nil reporter and panics. Ignore nil or normalize it tostatus.Nop().Proposed fix
func WithStatusReporter(reporter status.Reporter) Option { return func(s *tunnelServer) *tunnelServer { - s.statusReporter = reporter + if reporter != nil { + s.statusReporter = reporter + } return s } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/agent/tunnelserver/options.go` around lines 80 - 83, Update WithStatusReporter so passing a nil reporter does not replace the tunnelServer’s existing status.Nop() reporter; ignore nil or normalize it to status.Nop(), while continuing to assign non-nil reporters.
🧹 Nitpick comments (1)
desktop/src/main/__tests__/ipc-up-tasks.test.ts (1)
93-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering the streamed-envelope path too.
The three tests cover cancel/serialize/retain-on-failure well, but nothing exercises the
parseCliEnvelopebranch inworkspace_up:statusenvelopes emittingworkspace-status, andresult/errorenvelopes releasing the task and completing the sink before the exit callback. Driving therunStreamingonLinecallback with a few NDJSON lines would lock in that contract cheaply, and would also catch agetMainWindow()returningnullregression (the current double always returnsnull, so the send path is never hit).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/main/__tests__/ipc-up-tasks.test.ts` around lines 93 - 154, Extend the workspace_up test coverage to drive runStreaming’s onLine callback with NDJSON envelopes parsed by parseCliEnvelope: verify status envelopes emit workspace-status, while result and error envelopes release the task and complete the sink before the exit callback. Update the getMainWindow test double to return a window so the send path is exercised, while retaining coverage for the null-window case if supported.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@desktop/src/main/ipc.ts`:
- Around line 801-817: Validate submitted.id immediately after cli.run in the
existing try block before assigning taskId or updating activeUpTasks; if it is
missing or invalid, throw a clear error so the existing catch routes the failure
through sink.done with the standard error metadata and returns cmdId.
---
Outside diff comments:
In `@desktop/src/main/ipc.ts`:
- Around line 239-242: Update quiesceWorkspace to execute cancelActiveUp and the
cli/pty cancellation operations through serializePerWorkspace for the given
workspaceId, ensuring it joins the same chain used by workspace_up. Preserve the
existing cancellation ordering and Promise.all behavior while preventing
stop/delete from racing an in-flight submission.
In `@pkg/agent/tunnelserver/options.go`:
- Around line 80-83: Update WithStatusReporter so passing a nil reporter does
not replace the tunnelServer’s existing status.Nop() reporter; ignore nil or
normalize it to status.Nop(), while continuing to assign non-nil reporters.
In `@pkg/client/clientimplementation/workspace_client.go`:
- Around line 336-380: Update latestUpTask and taskStatusOverride so persisted
non-terminal up tasks are reconciled before returning StatusProvisioning. Detect
abandoned tasks using the task worker’s existing liveness/heartbeat expiry or
startup reconciliation mechanism, mark them terminal/failed as appropriate, and
only report provisioning for an actively running task; preserve the existing
best-effort ok=false behavior when task state cannot be read.
---
Nitpick comments:
In `@desktop/src/main/__tests__/ipc-up-tasks.test.ts`:
- Around line 93-154: Extend the workspace_up test coverage to drive
runStreaming’s onLine callback with NDJSON envelopes parsed by parseCliEnvelope:
verify status envelopes emit workspace-status, while result and error envelopes
release the task and complete the sink before the exit callback. Update the
getMainWindow test double to return a window so the send path is exercised,
while retaining coverage for the null-window case if supported.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d0634d59-0d8e-4640-86c6-543dacef7c11
📒 Files selected for processing (24)
cmd/internal/container_tunnel.gocmd/workspace/task.gocmd/workspace/up/status.gocmd/workspace/up/up.godesktop/src/main/__tests__/ipc-up-tasks.test.tsdesktop/src/main/ipc.tspkg/agent/tunnelserver/options.gopkg/agent/tunnelserver/status_sender.gopkg/agent/tunnelserver/tunnelserver.gopkg/client/client.gopkg/client/clientimplementation/daemonclient/stop.gopkg/client/clientimplementation/daemonclient/up.gopkg/client/clientimplementation/daemonclient/up_test.gopkg/client/clientimplementation/workspace_client.gopkg/devcontainer/build.gopkg/devcontainer/config/envelope.gopkg/devcontainer/run.gopkg/devcontainer/setup.gopkg/devcontainer/single.gopkg/status/log.gopkg/status/status.gopkg/status/status_test.gopkg/task/task.gopkg/task/task_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
- pkg/agent/tunnelserver/tunnelserver.go
- pkg/task/task.go
- pkg/client/clientimplementation/daemonclient/up.go
- cmd/internal/container_tunnel.go
- pkg/devcontainer/setup.go
- pkg/devcontainer/config/envelope.go
- cmd/workspace/up/up.go
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
desktop/src/main/ipc.ts (1)
877-889: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winKeep the task registered when the log follower exits unexpectedly.
Line 878 releases
taskIdeven when noresultorerrorenvelope arrived. A failed follower can therefore orphan a still-running detached task from later cancellation. Release only on a terminal envelope (or after independently confirming the task is terminal).Proposed fix
(code, cliError) => { - releaseTask() if (tunnelProcesses.get(wsId) === child) { tunnelProcesses.delete(wsId) } if (signalledDone) returnAdd a regression test where the follower exits nonzero without an envelope, then verify the next
workspace_upcancels the original task.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/main/ipc.ts` around lines 877 - 889, Update the follower exit callback around releaseTask so an unexpected nonzero exit without a result or error envelope does not unregister the still-running task; release the task only after receiving a terminal envelope or independently confirming termination. Preserve cleanup of tunnelProcesses and sink reporting, and add a regression test verifying that a subsequent workspace_up cancels the original task.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/task/store.go`:
- Around line 113-122: Update Abandoned to avoid treating PID liveness alone as
worker identity: persist a worker-instance identity alongside State.PID and
verify that identity for the current process before considering the task alive.
If the identity is missing, mismatched, or the process is no longer running,
return true for a non-terminal task; preserve the existing safe behavior when
liveness or identity cannot be determined.
In `@pkg/task/task.go`:
- Around line 17-20: Remove the duplicate ErrAbandoned declaration from the
package-level var block in task.go, preserving the existing declaration in
store.go and retaining ErrCanceled unchanged.
---
Outside diff comments:
In `@desktop/src/main/ipc.ts`:
- Around line 877-889: Update the follower exit callback around releaseTask so
an unexpected nonzero exit without a result or error envelope does not
unregister the still-running task; release the task only after receiving a
terminal envelope or independently confirming termination. Preserve cleanup of
tunnelProcesses and sink reporting, and add a regression test verifying that a
subsequent workspace_up cancels the original task.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ed8f5388-9dac-41fd-a1ba-6367f893f6bc
⛔ Files ignored due to path filters (1)
pkg/agent/tunnel/tunnel.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (25)
cmd/workspace/task.gocmd/workspace/up/detach.gocmd/workspace/up/status.gocmd/workspace/up/up.godesktop/e2e/fixtures/mock-devsy.cjsdesktop/e2e/workspaces.e2e.tsdesktop/src/main/__tests__/ipc-up-tasks.test.tsdesktop/src/main/ipc.tsdesktop/src/renderer/src/lib/types/index.tspkg/agent/tunnel/tunnel.protopkg/agent/tunnelserver/options.gopkg/client/client.gopkg/client/clientimplementation/workspace_client.gopkg/client/clientimplementation/workspace_client_status_test.gopkg/config/pathmanager.gopkg/devcontainer/config/envelope.gopkg/devcontainer/feature/extend.gopkg/devcontainer/feature/lockfile.gopkg/devcontainer/run.gopkg/devcontainer/setup.gopkg/status/log.gopkg/status/status.gopkg/task/store.gopkg/task/task.gopkg/task/task_test.go
💤 Files with no reviewable changes (4)
- pkg/agent/tunnel/tunnel.proto
- cmd/workspace/up/up.go
- pkg/config/pathmanager.go
- pkg/devcontainer/setup.go
🚧 Files skipped from review as they are similar to previous changes (9)
- pkg/status/log.go
- desktop/src/renderer/src/lib/types/index.ts
- pkg/devcontainer/config/envelope.go
- cmd/workspace/up/detach.go
- pkg/client/client.go
- pkg/devcontainer/feature/lockfile.go
- pkg/devcontainer/run.go
- pkg/devcontainer/feature/extend.go
- pkg/status/status.go
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/task/export_test_helpers.go (1)
1-17: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRename file so it's excluded from production builds.
export_test_helpers.godoes not end in_test.go, so Go compiles it into every regular build —ReleaseWorkerLockForTest()andSetAfterClaimForTest()(which force-drop a held worker lock / inject a callback intoReconcile's locked critical section) ship as exported production API, not gated behindgo test. These directly bypass the worker-lock exclusivity this PR relies on to prevent duplicate detached workers.🔒 Proposed fix — use the real `_test.go` suffix
-pkg/task/export_test_helpers.go +pkg/task/export_test.go🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/task/export_test_helpers.go` around lines 1 - 17, Rename export_test_helpers.go to a filename ending in _test.go so ReleaseWorkerLockForTest and SetAfterClaimForTest are compiled only during tests and excluded from production builds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/compose/helper_test.go`:
- Line 191: Update the reachability probe in the affected test to use the
existing testPodmanCmd symbol instead of the hardcoded "podman" executable,
while preserving the current compose version check and skip/failure behavior.
---
Outside diff comments:
In `@pkg/task/export_test_helpers.go`:
- Around line 1-17: Rename export_test_helpers.go to a filename ending in
_test.go so ReleaseWorkerLockForTest and SetAfterClaimForTest are compiled only
during tests and excluded from production builds.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 890d35d8-39f8-47e4-adba-cb41be6f9c97
📒 Files selected for processing (12)
cmd/workspace/up/detach.gocmd/workspace/up/up.godesktop/src/main/__tests__/ipc-up-tasks.test.tsdesktop/src/main/ipc.tspkg/client/clientimplementation/workspace_client_status_test.gopkg/compose/helper_test.gopkg/config/pathmanager.gopkg/devcontainer/config/envelope.gopkg/task/export_test_helpers.gopkg/task/store.gopkg/task/task.gopkg/task/task_test.go
💤 Files with no reviewable changes (1)
- pkg/devcontainer/config/envelope.go
🚧 Files skipped from review as they are similar to previous changes (2)
- cmd/workspace/up/up.go
- pkg/config/pathmanager.go
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
desktop/src/main/ipc.ts (1)
821-832: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winHandle log-follower startup failures after detached submission.
cli.runStreaming(...)runs outside the existingtry. If it rejects, the background task has already been created, but the handler rejects without completingsink, leaving the log open and the task tracked without a follower process. Catch this failure, complete the sink with an error, and retain the task ID for later cancellation/reconciliation.🛠️ Proposed fix
activeUpTasks.set(wsId, taskId) - const child = await cli.runStreaming( - ["workspace", "task", "logs", taskId, "--follow"], - ... - ) + let child + try { + child = await cli.runStreaming( + ["workspace", "task", "logs", taskId, "--follow"], + ... + ) + } catch (error) { + const err = error as Error & { cliError?: CLIError } + void sink.done(formatLogLine(err.message, "ERROR"), { + level: "error", + cliError: err.cliError ?? { + code: "up_follow_failed", + message: err.message, + }, + }) + return cmdId + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/main/ipc.ts` around lines 821 - 832, Handle rejection from cli.runStreaming in the task-log handler after detached submission: catch startup failures, complete sink with the error, and preserve the submitted taskId for later cancellation or reconciliation. Keep the releaseTask ownership guard intact and ensure activeUpTasks is not cleared when no follower process starts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@desktop/src/main/ipc.ts`:
- Around line 821-832: Handle rejection from cli.runStreaming in the task-log
handler after detached submission: catch startup failures, complete sink with
the error, and preserve the submitted taskId for later cancellation or
reconciliation. Keep the releaseTask ownership guard intact and ensure
activeUpTasks is not cleared when no follower process starts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 51533f7c-f14a-4e51-8efd-52eb1d4f4329
📒 Files selected for processing (7)
desktop/src/main/__tests__/ipc-up-tasks.test.tsdesktop/src/main/ipc.tspkg/agent/tunnelserver/options.gopkg/compose/helper_test.gopkg/task/export_test.gopkg/task/store.gopkg/task/task.go
🚧 Files skipped from review as they are similar to previous changes (2)
- pkg/agent/tunnelserver/options.go
- pkg/compose/helper_test.go
3ee2366 to
4206641
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (2)
cmd/workspace/task.go (1)
51-73: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winConsider using the same envelope contract as
task get/task logs.
taskListCmd.runJSON-encodesstates([]*task.State) directly withjson.NewEncoder(os.Stdout).Encode(states). Every other JSON output path in this file (reportTaskStateJSON, used bytask get,task logs, indirectlytask cancel) goes throughconfig2.WriteStatusJSON/WriteResultJSON/WriteErrorJSON. Exposingtask.State's internal field names directly as thetask list --output jsoncontract diverges from the rest of the command group and makes the list output's shape change silently whenevertask.State's fields change.Wrap each state with the same envelope helpers (or a list-specific envelope) used elsewhere in this file.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/workspace/task.go` around lines 51 - 73, Update taskListCmd.run’s emitJSON branch to use the established config2 status/result envelope helpers, matching reportTaskStateJSON and the task get/logs/cancel JSON contract instead of encoding []*task.State directly. Preserve the existing state retrieval and human-readable output paths, and ensure each listed state is represented through the shared envelope or an equivalent list-specific envelope.pkg/status/status.go (1)
43-48: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valuePreserve step context for named
status.Failfailures.Current
status.Entercalls pass an empty step, but failures now reportStep: string(phase). If multi-step phases such asPhaseRunningLifecycleHookorPhaseWaitingForreport meaningful sub-steps later,status.Failshould accept astepargument and reporters should keep failing that diagnostic path, including consumers likecmd/workspace/up/status.go.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/status/status.go` around lines 43 - 48, Update status.Fail to accept a step argument and use it for Event.Step instead of deriving the step from phase. Preserve the existing PhaseFailed and error reporting behavior, then update all status.Fail callers, including cmd/workspace/up/status.go, to pass the relevant step context while keeping existing Enter calls compatible.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@cmd/workspace/task.go`:
- Around line 51-73: Update taskListCmd.run’s emitJSON branch to use the
established config2 status/result envelope helpers, matching reportTaskStateJSON
and the task get/logs/cancel JSON contract instead of encoding []*task.State
directly. Preserve the existing state retrieval and human-readable output paths,
and ensure each listed state is represented through the shared envelope or an
equivalent list-specific envelope.
In `@pkg/status/status.go`:
- Around line 43-48: Update status.Fail to accept a step argument and use it for
Event.Step instead of deriving the step from phase. Preserve the existing
PhaseFailed and error reporting behavior, then update all status.Fail callers,
including cmd/workspace/up/status.go, to pass the relevant step context while
keeping existing Enter calls compatible.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4bcc0bde-3b0d-4714-a5be-8970657972eb
⛔ Files ignored due to path filters (2)
pkg/agent/tunnel/tunnel.pb.gois excluded by!**/*.pb.gopkg/agent/tunnel/tunnel_grpc.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (47)
cmd/internal/agentworkspace/up.gocmd/internal/container_tunnel.gocmd/workspace/task.gocmd/workspace/task_test.gocmd/workspace/up/agent.gocmd/workspace/up/detach.gocmd/workspace/up/detach_test.gocmd/workspace/up/status.gocmd/workspace/up/up.gocmd/workspace/up/up_flags.gocmd/workspace/workspace.godesktop/e2e/fixtures/mock-devsy.cjsdesktop/e2e/workspaces.e2e.tsdesktop/src/main/__tests__/ipc-up-tasks.test.tsdesktop/src/main/ipc.tsdesktop/src/renderer/src/lib/ipc/events.tsdesktop/src/renderer/src/lib/types/index.tsdesktop/src/shared/cli-error.tspkg/agent/tunnel/tunnel.protopkg/agent/tunnelserver/options.gopkg/agent/tunnelserver/status_sender.gopkg/agent/tunnelserver/tunnelserver.gopkg/client/client.gopkg/client/clientimplementation/daemonclient/stop.gopkg/client/clientimplementation/daemonclient/up.gopkg/client/clientimplementation/daemonclient/up_test.gopkg/client/clientimplementation/workspace_client.gopkg/client/clientimplementation/workspace_client_status_test.gopkg/command/process_supported.gopkg/command/process_test.gopkg/compose/helper_test.gopkg/config/pathmanager.gopkg/devcontainer/build.gopkg/devcontainer/config/envelope.gopkg/devcontainer/feature/extend.gopkg/devcontainer/feature/lockfile.gopkg/devcontainer/run.gopkg/devcontainer/setup.gopkg/devcontainer/single.gopkg/flags/names/names.gopkg/status/log.gopkg/status/status.gopkg/status/status_test.gopkg/task/export_test.gopkg/task/store.gopkg/task/task.gopkg/task/task_test.go
🚧 Files skipped from review as they are similar to previous changes (38)
- cmd/internal/container_tunnel.go
- desktop/e2e/workspaces.e2e.ts
- cmd/internal/agentworkspace/up.go
- pkg/devcontainer/feature/lockfile.go
- cmd/workspace/up/agent.go
- pkg/devcontainer/single.go
- desktop/src/renderer/src/lib/ipc/events.ts
- pkg/config/pathmanager.go
- cmd/workspace/up/up_flags.go
- pkg/devcontainer/setup.go
- pkg/agent/tunnelserver/status_sender.go
- pkg/compose/helper_test.go
- pkg/devcontainer/build.go
- pkg/agent/tunnel/tunnel.proto
- pkg/client/clientimplementation/daemonclient/stop.go
- cmd/workspace/task_test.go
- cmd/workspace/up/detach_test.go
- pkg/command/process_supported.go
- pkg/flags/names/names.go
- pkg/command/process_test.go
- desktop/src/shared/cli-error.ts
- cmd/workspace/up/up.go
- pkg/client/clientimplementation/daemonclient/up_test.go
- pkg/client/client.go
- pkg/agent/tunnelserver/tunnelserver.go
- pkg/devcontainer/run.go
- pkg/devcontainer/feature/extend.go
- pkg/client/clientimplementation/workspace_client.go
- pkg/client/clientimplementation/daemonclient/up.go
- pkg/devcontainer/config/envelope.go
- pkg/agent/tunnelserver/options.go
- desktop/e2e/fixtures/mock-devsy.cjs
- cmd/workspace/workspace.go
- pkg/task/export_test.go
- pkg/status/log.go
- pkg/status/status_test.go
- pkg/task/task.go
- pkg/task/store.go
Adds a submit/poll durable execution model alongside the existing synchronous flow: - pkg/task: a JSON-file task store (atomic writes, cross-process flock, worker-held liveness lock) recording status/PID/result for background-launched work. - workspace up --detach re-execs itself as a background process and returns a task ID immediately. - workspace task list/get/logs/cancel/rm: standard verb commands to manage submitted tasks. - pkg/status: a structured Phase/Event/Reporter model threaded through Runner.Up and the devcontainer build/run/setup pipeline, replacing ad hoc log lines with typed progress events. - A StatusUpdate RPC on the agent tunnel streams those events back to the CLI host live instead of waiting for the final result. - Desktop app updated to submit+poll via the new task model instead of parsing a single terminal JSON blob. Squashed from the individual commits on this branch, rebased onto main.
4206641 to
5a67e40
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/task/task_pid_supported_test.go`:
- Around line 38-74: The test TestCancelDoesNotSignalAProcessThatReusedThePID
exceeds the cyclop complexity limit because of its inline process-wait
assertion. Extract the final select that waits on exited into a small t.Helper
function, then call that helper from the test while preserving the existing
failure and timeout behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cc86f524-b894-4259-ba4c-e3e45b8eaba7
⛔ Files ignored due to path filters (2)
pkg/agent/tunnel/tunnel.pb.gois excluded by!**/*.pb.gopkg/agent/tunnel/tunnel_grpc.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (48)
cmd/internal/agentworkspace/up.gocmd/internal/container_tunnel.gocmd/workspace/task.gocmd/workspace/task_test.gocmd/workspace/up/agent.gocmd/workspace/up/detach.gocmd/workspace/up/detach_test.gocmd/workspace/up/status.gocmd/workspace/up/up.gocmd/workspace/up/up_flags.gocmd/workspace/workspace.godesktop/e2e/fixtures/mock-devsy.cjsdesktop/e2e/workspaces.e2e.tsdesktop/src/main/__tests__/ipc-up-tasks.test.tsdesktop/src/main/ipc.tsdesktop/src/renderer/src/lib/ipc/events.tsdesktop/src/renderer/src/lib/types/index.tsdesktop/src/shared/cli-error.tspkg/agent/tunnel/tunnel.protopkg/agent/tunnelserver/options.gopkg/agent/tunnelserver/status_sender.gopkg/agent/tunnelserver/tunnelserver.gopkg/client/client.gopkg/client/clientimplementation/daemonclient/stop.gopkg/client/clientimplementation/daemonclient/up.gopkg/client/clientimplementation/daemonclient/up_test.gopkg/client/clientimplementation/workspace_client.gopkg/client/clientimplementation/workspace_client_status_test.gopkg/command/process_supported.gopkg/command/process_test.gopkg/compose/helper_test.gopkg/config/pathmanager.gopkg/devcontainer/build.gopkg/devcontainer/config/envelope.gopkg/devcontainer/feature/extend.gopkg/devcontainer/feature/lockfile.gopkg/devcontainer/run.gopkg/devcontainer/setup.gopkg/devcontainer/single.gopkg/flags/names/names.gopkg/status/log.gopkg/status/status.gopkg/status/status_test.gopkg/task/export_test.gopkg/task/store.gopkg/task/task.gopkg/task/task_pid_supported_test.gopkg/task/task_test.go
🚧 Files skipped from review as they are similar to previous changes (44)
- desktop/e2e/workspaces.e2e.ts
- cmd/internal/agentworkspace/up.go
- cmd/internal/container_tunnel.go
- cmd/workspace/up/up_flags.go
- cmd/workspace/up/agent.go
- pkg/devcontainer/single.go
- pkg/devcontainer/feature/lockfile.go
- pkg/task/export_test.go
- pkg/status/log.go
- pkg/devcontainer/build.go
- pkg/flags/names/names.go
- pkg/command/process_supported.go
- cmd/workspace/up/detach.go
- pkg/config/pathmanager.go
- cmd/workspace/up/status.go
- cmd/workspace/up/detach_test.go
- desktop/e2e/fixtures/mock-devsy.cjs
- pkg/task/task.go
- pkg/compose/helper_test.go
- pkg/agent/tunnelserver/status_sender.go
- pkg/client/clientimplementation/daemonclient/stop.go
- pkg/devcontainer/setup.go
- cmd/workspace/task.go
- desktop/src/main/tests/ipc-up-tasks.test.ts
- pkg/agent/tunnelserver/options.go
- pkg/command/process_test.go
- cmd/workspace/up/up.go
- pkg/agent/tunnel/tunnel.proto
- desktop/src/shared/cli-error.ts
- pkg/client/client.go
- pkg/status/status_test.go
- desktop/src/renderer/src/lib/types/index.ts
- pkg/client/clientimplementation/daemonclient/up_test.go
- cmd/workspace/workspace.go
- pkg/client/clientimplementation/workspace_client.go
- desktop/src/renderer/src/lib/ipc/events.ts
- pkg/agent/tunnelserver/tunnelserver.go
- pkg/client/clientimplementation/workspace_client_status_test.go
- pkg/status/status.go
- pkg/task/store.go
- pkg/task/task_test.go
- pkg/client/clientimplementation/daemonclient/up.go
- pkg/devcontainer/run.go
- pkg/devcontainer/config/envelope.go
Fixes still open after the squash/rebase (most other review findings were already addressed by the branch's prior commits): - Task.Cancel: skip signaling by PID when the task's worker lock is free, so a dead worker's recycled PID can't be handed to an unrelated process. - taskReporter.Report: a PhaseFailed event now sets Status/Phase/Step, not just Error, so the task is actually left terminal. - workspace task rm: emit a JSON result payload; failed-task messages never render blank. - workspace up detach: record HoldWorkerLock/SetPID failures on the task instead of leaving it stuck non-terminal. - process_test.go: restrict to the platforms process_supported.go supports. - pkg/status/log.go: include Event.Step in non-failure log lines. - mock-devsy.cjs: match the real CLI's task existence/force/terminal semantics for cancel, rm, and logs, and recognize --detach=true/-d.
5a67e40 to
337acf2
Compare
…for task list - Revert the Fprintln(Sprintf(...)) experiment flagged by staticcheck (S1038) back to plain Fprintf. - workspace task list now renders plain output via pkg/table, and JSON output via json.MarshalIndent, matching every other 'list' command (cmd/ide/list.go, cmd/pro/list.go, etc.) instead of a hand-rolled tab-separated line and a compact encoder. Safe here because desktop's cli.run() JSON.parse()s the whole captured stdout rather than reading NDJSON line-by-line; that per-line contract only applies to the status/result/error envelope writers used during streaming, which this command doesn't touch.
A line matching {"kind":"status",...} but missing phase or started
isn't one of ours, yet was accepted as a valid event, which could
zero out a task's Phase or spuriously flip it Pending -> Running.
Reachable only if unrelated output sharing the same stream (build/pull
logs, etc.) happens to produce such a line, but the check is cheap.
Started becomes *bool on the wire so an omitted field is
distinguishable from an explicit false; WriteStatusJSON always sets it,
so legitimate output is unaffected.
Adding a provider showed a red "not initialized" badge for several seconds before flipping to "initialized". provider add --use=false persists the provider to config.yaml before init runs, and parseProviderEntries collapsed "not yet known" and "genuinely not initialized" into the same false, so the card rendered the destructive badge for both. Two lifecycle axes were collapsed into one boolean. They're now separate: - Persisted truth stays Initialized bool on disk: "did this binary run Exec.Init". No schema change, no migration. - Transient job state moves to a main-process ProviderJobs registry, fed by the CLI's structured status events and broadcast on the existing providers-changed channel. Main-process ownership means it survives the wizard closing, navigation, and window reload. This also distinguishes states the flag cannot: installing vs initializing vs updating, and failed vs never-initialized. Also included: - provider add/init/set-source emit structured progress (pkg/status Phase/Event/Reporter, as workspace up does), replacing the desktop's "Exit code: 0" string-sniffing. pkg/devcontainer/status -> pkg/status, since it's no longer devcontainer-specific. - Correctness fix: set-source left Initialized stale, so a provider updated to a new binary still reported initialized: true though the new binary's init never ran. Update and version-pin now chain provider init. - runStreaming hang: it registered close but not error, so a spawn failure never invoked onExit, hanging callers and leaking the concurrency slot. Affected every streaming command, not just providers. Squashed from the individual commits on this branch, rebased onto main after #798 merged. Conflicts resolved by keeping both branches' independent fixes (e.g. task PID-reuse guard, envelope validation) alongside this branch's new Pipeline/ProviderJobs work.
) * fix(provider): represent provider install/init lifecycle in the UI Adding a provider showed a red "not initialized" badge for several seconds before flipping to "initialized". provider add --use=false persists the provider to config.yaml before init runs, and parseProviderEntries collapsed "not yet known" and "genuinely not initialized" into the same false, so the card rendered the destructive badge for both. Two lifecycle axes were collapsed into one boolean. They're now separate: - Persisted truth stays Initialized bool on disk: "did this binary run Exec.Init". No schema change, no migration. - Transient job state moves to a main-process ProviderJobs registry, fed by the CLI's structured status events and broadcast on the existing providers-changed channel. Main-process ownership means it survives the wizard closing, navigation, and window reload. This also distinguishes states the flag cannot: installing vs initializing vs updating, and failed vs never-initialized. Also included: - provider add/init/set-source emit structured progress (pkg/status Phase/Event/Reporter, as workspace up does), replacing the desktop's "Exit code: 0" string-sniffing. pkg/devcontainer/status -> pkg/status, since it's no longer devcontainer-specific. - Correctness fix: set-source left Initialized stale, so a provider updated to a new binary still reported initialized: true though the new binary's init never ran. Update and version-pin now chain provider init. - runStreaming hang: it registered close but not error, so a spawn failure never invoked onExit, hanging callers and leaking the concurrency slot. Affected every streaming command, not just providers. Squashed from the individual commits on this branch, rebased onto main after #798 merged. Conflicts resolved by keeping both branches' independent fixes (e.g. task PID-reuse guard, envelope validation) alongside this branch's new Pipeline/ProviderJobs work. * fix(desktop): close remaining gaps from CodeRabbit review Confirmed real by verification, not just the review's say-so: - watcher.ts: refreshProviders() called pollProviders() directly, bypassing the polling/pollQueued coordination schedulePoll uses. A manual refresh (e.g. after an install finishes) could run concurrently with a scheduled poll, and whichever CLI call landed last could overwrite the other's result — reintroducing the same "stale state wins" bug class this PR exists to fix. Both paths now go through a serializing queue scoped to provider polls. Added a unit test that fails against the old code (proved by reverting locally) and passes with the fix. - ipc.ts: runProviderWithStatus discarded cli.runStreaming's promise with `void`, so a rejection from a failure before onExit is wired up would hang the caller forever instead of surfacing. Low probability given runStreaming's own error handling, but a zero-risk safety net. - providers.e2e.ts: wrapped both provider lifecycle tests' assertions in try/finally so a failed assertion still runs the provider_delete cleanup, instead of leaking dirty mock CLI state into later specs. Skipped two other findings from the same review after tracing actual consumers: - cmd/provider/add.go's status.Enter uses the pre-resolution provider name (empty when --name is omitted). No current consumer (CLI plain text output, desktop's job tracking) reads that field for this event, so there's no observable effect to fix. - provider-jobs.ts's generation tracking already prevents a delayed release from clobbering a newer job — verified via an existing test (`does not clear a newer job started while refresh was in flight`). The suggested renderer-side token would be redundant. * style(desktop): trim a comment duplicating the field doc above it * test(desktop): make the second watcher test assert ordering, not just count The prior version asserted callCount === 2 after two concurrent refreshProviders() calls, which passes whether or not they're serialized — two concurrent queries and two queued ones both finish two queries. Recording start/end order is what actually distinguishes them; verified by reverting the fix locally and confirming this version fails while the count-based one didn't. * fix(desktop): stop finish() from misattributing errors and resurrecting cleared jobs Both confirmed with reproductions before fixing (see the two new tests, which fail against the prior code and pass against these fixes): - withProviderJob's success-path finish(name) call lived inside the try block, so a rejection from it (e.g. the refresh it awaits failing) was caught by the catch clause and re-reported via finish(name, <refresh error>) as if the operation itself had failed, discarding that fn() actually succeeded. Moved the success-path finish() outside the try/catch. - ProviderJobs.finish()'s error branch wrote a new "failed" job entry unconditionally, even when the job had already been cleared (e.g. by a concurrent provider_delete). A delayed/stale error arriving after that point resurrected UI activity for a provider the user already considers gone — the same invariant report() already documents and enforces. Now returns early when the job is gone. * fix(workspace): stream the detached worker's real log output over --follow Since the async up model landed, the desktop no longer runs `workspace up` directly and streams its output live — it submits `--detach` and polls `workspace task logs --follow`. The worker's real stdout/stderr get redirected to a *.streams file (pkg/command.StartBackground) that nothing ever read back, so the UI only ever saw synthesized phase-transition lines. Enabling debug mode had no effect because the worker's actual log lines, at any level, never reached it. followTask now tails that streams file each poll and forwards new lines to stderr, skipping structured NDJSON envelopes (status/result/ error) since those are already reported from polled task state and would otherwise show up twice. Verified with the CLI directly first (confirmed --debug does emit debug-level lines, and confirmed nothing read the streams file before this change), then with unit tests for the tailer and an end-to-end test that runs followTask against a real task while concurrently writing to its streams file. * fix(desktop): show a Deleting badge on the Workspaces list while removal is in flight Mirrors the ProviderJobs pattern with a WorkspaceJobs registry so the UI reflects an in-progress delete instead of showing stale status until the next poll picks up the CLI's exit. * fix(desktop): close CodeRabbit-confirmed gaps in the workspace delete job - WorkspaceJobs.finish() now requires the generation start() returned, so a stale exit callback can't misattribute its result to a retry that already superseded it. - A failed delete no longer keeps the row disabled forever: it drops out of "deleting" and shows a dismissible "Delete failed" badge, matching the provider status convention. - mock-devsy.cjs's delayed delete re-reads persisted state instead of writing back a stale in-memory snapshot, matching the existing provider-init fixture's pattern. - workspaces.e2e.ts's intentionally unawaited delete call now swallows its rejection instead of risking an unhandled rejection. * fix(workspace): satisfy gosec G304 on the tailer test's append-open CI's golangci-lint run flagged the variable path passed to os.OpenFile in the append-and-repoll test; it's t.TempDir()-derived, same as the file's other os.WriteFile calls. * style: trim verbose comments to their essential why Several multi-line comments restated context already clear from the surrounding code; condensed to one line each. * fix(desktop): use the correct workspace_up param name in the delete e2e test Passed "id" instead of "workspaceId", so the main process fell back to the raw source URL as the log-path segment. Windows rejects the colon in "https://", failing mkdir; Unix tolerates it, so this only surfaced on the Windows CI runner. * fix(desktop): stop the workspace-delete IPC test racing a real timer waitForExitCallback() slept a fixed 10ms hoping the mock's setTimeout(fn, 0) had already fired. Fake timers make the wait deterministic instead. * style: clean comments
workspace up currently runs synchronously end-to-end, streaming a single JSON blob to the caller only once the container is ready. That blocks the CLI/desktop for the whole provisioning window and gives up all in-progress state on a crash or disconnect.
Adds a submit/poll durable execution model alongside the existing synchronous flow:
pkg/task: a JSON-file task store (atomic writes, cross-process flock) recording status/PID/result for background-launched work.workspace up --detachre-execs itself as a background process and returns a task ID immediately.workspace task list/get/logs/cancel/rm: standard verb commands (kubectl/docker/gh-style) to manage submitted tasks.pkg/devcontainer/status: a structured Phase/Event/Reporter model threaded throughRunner.Upand the devcontainer build/run/setup pipeline, replacing ad hoc log lines with typed progress events.StatusUpdateRPC on the agent tunnel streams those events back to the CLI host live instead of waiting for the final result.See
docs/rfcs/async-workspace-up.mdfor the full design.Includes an unrelated one-line test fix in
pkg/compose/helper_test.go(skip when the local podman machine is unreachable, a machine-local CI-vs-local flake noticed during this work).Summary by CodeRabbit
New Features
Bug Fixes