fix(ui): workspace list and detail state views - #907
Conversation
✅ Deploy Preview for devsydev canceled.
|
📝 WalkthroughWalkthroughThe PR adds desktop dependency and Xvfb setup, extends workspace lifecycle behavior, preserves omitted workspace statuses, propagates command IDs, improves process cancellation cleanup, and adds workspace detail E2E coverage. ChangesWorkspace lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
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 images-devsy-sh canceled.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/e2e/fixtures/mock-devsy.cjs`:
- Around line 209-228: Update materializeWorkspace so existing workspace entries
retain their current source, provider, ide, and context metadata instead of
rebuilding those fields from command arguments. For an existing workspace, merge
only explicitly changed lifecycle fields such as status, lastUsed, and relevant
identifiers; keep the current initialization behavior for new workspaces.
In `@desktop/src/renderer/src/pages/WorkspaceDetailPage.svelte`:
- Around line 693-711: Update the Stop button’s disabled condition near
handleStop so it is enabled only when the workspace is running, including all
transitional states such as starting, stopping, and deleting. Do not rely solely
on operationRunning; use the existing isRunning state and preserve the current
behavior for non-running workspaces.
In `@Taskfile.yml`:
- Around line 149-152: Update the desktop:deps installation list to include
libnotify4, libxtst6, xdg-utils, and libuuid1, keeping it aligned with the
DESKTOP runtime dependencies declared by desktop/electron-builder.yml and the
installation documentation.
- Around line 195-198: Update the Xvfb setup in the task to verify that display
:99 is running specifically, rather than matching any Xvfb process. After
starting Xvfb for :99, add a readiness wait loop before invoking npm run
test:e2e so the test cannot race server initialization.
🪄 Autofix
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 52f264e9-b87b-4ddb-9551-42db3785298e
📒 Files selected for processing (9)
Taskfile.ymldesktop/e2e/fixtures/mock-devsy.cjsdesktop/e2e/workspaces.e2e.tsdesktop/src/main/__tests__/state.test.tsdesktop/src/main/state.tsdesktop/src/renderer/src/lib/stores/workspaces.test.tsdesktop/src/renderer/src/lib/stores/workspaces.tsdesktop/src/renderer/src/pages/WorkspaceDetailPage.sveltedesktop/src/renderer/src/pages/WorkspacesPage.svelte
| <Button | ||
| variant="destructive" | ||
| size="sm" | ||
| onclick={handleStop} | ||
| disabled={operationRunning || (!isRunning && !isBusy)} | ||
| > | ||
| {#if operationRunning && operationLabel === "Stop"}<Spinner />{:else}<Square class="h-4 w-4" />{/if} | ||
| Stop | ||
| </Button> | ||
|
|
||
| <Button | ||
| variant="default" | ||
| size="sm" | ||
| onclick={handleStart} | ||
| disabled={operationRunning || connecting || isRunning || isBusy || !isStopped} | ||
| > | ||
| {#if operationRunning && operationLabel === "Start"}<Spinner />{:else}<Play class="h-4 w-4" />{/if} | ||
| Start | ||
| </Button> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Disable Stop during every transitional status.
Line 697 enables Stop when isBusy is true. This includes starting, stopping, and deleting. If the page remounts while one of these operations is active, operationRunning is false and the user can dispatch a conflicting Stop command. Enable Stop only when the workspace is Running.
Proposed fix
- disabled={operationRunning || (!isRunning && !isBusy)}
+ disabled={operationRunning || !isRunning}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Button | |
| variant="destructive" | |
| size="sm" | |
| onclick={handleStop} | |
| disabled={operationRunning || (!isRunning && !isBusy)} | |
| > | |
| {#if operationRunning && operationLabel === "Stop"}<Spinner />{:else}<Square class="h-4 w-4" />{/if} | |
| Stop | |
| </Button> | |
| <Button | |
| variant="default" | |
| size="sm" | |
| onclick={handleStart} | |
| disabled={operationRunning || connecting || isRunning || isBusy || !isStopped} | |
| > | |
| {#if operationRunning && operationLabel === "Start"}<Spinner />{:else}<Play class="h-4 w-4" />{/if} | |
| Start | |
| </Button> | |
| <Button | |
| variant="destructive" | |
| size="sm" | |
| onclick={handleStop} | |
| disabled={operationRunning || !isRunning} | |
| > | |
| {`#if` operationRunning && operationLabel === "Stop"}<Spinner />{:else}<Square class="h-4 w-4" />{/if} | |
| Stop | |
| </Button> | |
| <Button | |
| variant="default" | |
| size="sm" | |
| onclick={handleStart} | |
| disabled={operationRunning || connecting || isRunning || isBusy || !isStopped} | |
| > | |
| {`#if` operationRunning && operationLabel === "Start"}<Spinner />{:else}<Play class="h-4 w-4" />{/if} | |
| Start | |
| </Button> |
🤖 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/renderer/src/pages/WorkspaceDetailPage.svelte` around lines 693 -
711, Update the Stop button’s disabled condition near handleStop so it is
enabled only when the workspace is running, including all transitional states
such as starting, stopping, and deleting. Do not rely solely on
operationRunning; use the existing isRunning state and preserve the current
behavior for non-running workspaces.
Fixes Applied SuccessfullyFixed 3 file(s) based on 4 unresolved review comments. Files modified:
Commit: The changes have been pushed to the Time taken: |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
desktop/src/renderer/src/lib/ipc/commands.ts (1)
50-52: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd explicit command ID assertions.
The adjacent test in
desktop/src/renderer/src/lib/ipc/commands.test.ts, Lines [80-85], only callsworkspaceStop("ws-1"). It does not verify the newcommandIdfield. Add explicit-ID assertions for all five workspace wrappers, or confirm that equivalent coverage exists elsewhere.Example assertion
+it("workspaceStop forwards commandId", async () => { + await workspaceStop("ws-1", false, "cmd-1") + expect(mockInvoke).toHaveBeenCalledWith("workspace_stop", { + workspaceId: "ws-1", + debug: false, + commandId: "cmd-1", + }) +})Also applies to: 58-60, 66-68, 74-76, 82-84
🤖 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/renderer/src/lib/ipc/commands.ts` around lines 50 - 52, Add explicit commandId coverage for all five workspace wrapper functions in the related tests, including workspaceUp and the wrappers at the additional referenced locations. Invoke each with a command ID and assert the underlying IPC call receives that ID, or reuse equivalent existing coverage if already present..github/workflows/commit.yml (1)
27-27: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBound the GitHub API request.
Add connection and transfer timeouts, bounded retries, and
--failor--fail-with-body. Curl does not treat HTTP error responses as command failures by default, so an API error is passed tojqand can produce an opaque failure. Validate the response shape before filtering. (curl.se)🤖 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 @.github/workflows/commit.yml at line 27, Update the curl invocation in the unsigned_commits assignment to use connection and transfer timeouts, bounded retries, and HTTP failure handling via --fail or --fail-with-body. Validate that the API response has the expected array shape before passing it to jq, preserving the existing commit filtering 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 @.github/workflows/commit.yml:
- Line 27: Update the unsigned_commits retrieval in the commit workflow to
paginate the pull-request commits endpoint through its 250-commit limit, then
query the repository commits endpoint for any remaining pull-request commits.
Combine all fetched commits before applying the existing verification and author
exclusions, preserving the current approval behavior for signed commits and
exempt authors.
- Line 27: Update the unsigned-commit filter in the workflow’s unsigned_commits
assignment to exclude only the trusted bot identity via an exact match on the
appropriate GitHub author/committer login or ID, or canonical bot email;
explicitly apply the exception to the author, committer, or both, and remove
substring checks against raw commit author name or email.
In `@desktop/src/main/ipc.ts`:
- Around line 232-248: Prevent quiescence waits from resolving while child
processes can still emit workspace callbacks: in desktop/src/main/ipc.ts lines
232-248, retain ownership of tunnelProc, terminate it, or suppress late
sink.done callbacks before resolving; in desktop/src/main/cli.ts lines 350-366,
block late onLine callbacks and release child tracking when the process does not
close; in desktop/src/main/pty.ts lines 107-117, fully detach or terminate the
PTY before resolution. Add regression tests covering each process remaining open
beyond two seconds and asserting no workspace callbacks occur after quiescence.
---
Nitpick comments:
In @.github/workflows/commit.yml:
- Line 27: Update the curl invocation in the unsigned_commits assignment to use
connection and transfer timeouts, bounded retries, and HTTP failure handling via
--fail or --fail-with-body. Validate that the API response has the expected
array shape before passing it to jq, preserving the existing commit filtering
behavior.
In `@desktop/src/renderer/src/lib/ipc/commands.ts`:
- Around line 50-52: Add explicit commandId coverage for all five workspace
wrapper functions in the related tests, including workspaceUp and the wrappers
at the additional referenced locations. Invoke each with a command ID and assert
the underlying IPC call receives that ID, or reuse equivalent existing coverage
if already present.
🪄 Autofix
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0d74a76c-5063-41da-9a6b-4a672adc08dc
📒 Files selected for processing (8)
.github/workflows/commit.ymlTaskfile.ymldesktop/e2e/fixtures/mock-devsy.cjsdesktop/src/main/cli.tsdesktop/src/main/ipc.tsdesktop/src/main/pty.tsdesktop/src/renderer/src/lib/ipc/commands.tsdesktop/src/renderer/src/pages/WorkspaceDetailPage.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
- desktop/src/renderer/src/pages/WorkspaceDetailPage.svelte
- Taskfile.yml
- desktop/e2e/fixtures/mock-devsy.cjs
| exit 1 | ||
| fi | ||
|
|
||
| unsigned_commits="$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$COMMITS_URL" | jq '.[] | select(.commit.verification.verified == false and (.commit.author.email | contains("google-labs-jules") | not) and (.commit.author.name | contains("google-labs-jules") | not)) | .commit.message')" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow files =="
git ls-files .github/workflows || true
echo "== commit.yml excerpt =="
if [ -f .github/workflows/commit.yml ]; then
cat -n .github/workflows/commit.yml | sed -n '1,120p'
fi
echo "== search for COMMITS_URL and pull request commit endpoints =="
rg -n "COMMITS_URL|pulls/.*/commits|page=|per_page|Link|unsigned_commits|GITHUB_TOKEN" .github/workflows || trueRepository: devsy-org/devsy
Length of output: 3615
🌐 Web query:
GitHub REST API list pull request commits pagination default per_page limit comparison commits endpoint
💡 Result:
In the GitHub REST API, both the "List commits on a pull request" and "Compare two commits" endpoints support pagination using the per_page and page query parameters [1][2]. For these endpoints, the default value for the per_page parameter is 30, and the maximum allowed value is 100 [1][2]. If a value greater than 100 is specified, GitHub will automatically reduce it to the maximum of 100 without returning an error [3][4]. Key details for these endpoints include: List commits on a pull request: While this endpoint supports pagination, it is documented to list a maximum of 250 commits for a pull request [1][5]. If you need to access a complete commit list for pull requests exceeding this limit, GitHub recommends using the "List commits" endpoint on a repository [1][5][6]. Compare two commits: This endpoint also supports pagination [7][2]. When called without pagination parameters, it is limited to returning 250 commits [2][8]. When you enable pagination by using per_page or page, the results are returned in chronological order, although the list of changed files remains restricted to the first page (with a limit of up to 300 changed files) [2][8].
Citations:
- 1: https://docs.github.com/en/rest/pulls/pulls
- 2: https://docs.github.com/en/rest/commits/commits?apiVersion=2026-03-10
- 3: https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2026-03-10
- 4: https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api
- 5: https://docs.github.com/en/rest/pulls/pulls?apiVersion=2026-03-10
- 6: https://github.com/github/developer.github.com/blob/master/content/v3/pulls.md
- 7: https://github.blog/changelog/2021-03-22-compare-rest-api-now-supports-pagination/
- 8: https://docs.github.com/en/enterprise-cloud@latest/rest/commits/commits
Fetch every pull-request commit before accepting the check.
This request reads only the first API page. GitHub defaults the pull-request commits endpoint to 30 results and caps this endpoint at 250 commits. Paginate up to the endpoint limit and use the repository commits endpoint for any remaining commits. Unsigned commits returned outside this check can therefore bypass approval.
🤖 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 @.github/workflows/commit.yml at line 27, Update the unsigned_commits
retrieval in the commit workflow to paginate the pull-request commits endpoint
through its 250-commit limit, then query the repository commits endpoint for any
remaining pull-request commits. Combine all fetched commits before applying the
existing verification and author exclusions, preserving the current approval
behavior for signed commits and exempt authors.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow excerpt =="
sed -n '1,80p' .github/workflows/commit.yml
echo
echo "== related workflow files =="
shopt -s nullglob
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
echo "--- $f ---"
wc -l "$f"
doneRepository: devsy-org/devsy
Length of output: 2359
🌐 Web query:
GitHub REST API Pulls list commits response author committer verification fields
💡 Result:
When using the GitHub REST API to list commits on a pull request (GET /repos/{owner}/{repo}/pulls/{pull_number}/commits), the response returns an array of commit objects [1][2]. Each commit object contains a nested commit object that includes details about the author, the committer, and the signature verification status [1][3]. Author and Committer Fields Within the nested commit object, both author and committer are represented as objects containing the following fields [1][4]: - name: The name of the user (string) [1]. - email: The email address associated with the user (string) [1]. - date: The date and time the action occurred (string, ISO 8601 format) [1]. These fields can be null if the information is missing from the Git commit data [1][5]. Verification Object The commit object also includes a verification object, which provides the results of GitHub's signature verification process for the commit [6][7]. It contains the following fields: - verified (boolean): Indicates whether GitHub considers the commit signature to be valid [6][7]. - reason (string): The explanation for the verification status (e.g., 'valid', 'unsigned', 'expired_key', 'bad_email') [6][7]. - signature (string or null): The actual signature extracted from the commit [6][7]. - payload (string or null): The data that was signed [6][7]. - verified_at (string or null): The timestamp of when GitHub verified the signature [1][6].
Citations:
- 1: https://docs.github.com/en/rest/pulls/pulls
- 2: https://docs.github.com/en/rest/pulls/pulls?apiVersion=2026-03-10
- 3: https://docs.github.com/en/enterprise-cloud@latest/rest/pulls/pulls
- 4: https://docs.github.com/en/enterprise-server@3.17/rest/pulls/pulls
- 5: https://docs.github.com/en/rest/commits/commits
- 6: https://docs.github.com/en/rest/commits/commits?apiVersion=2026-03-10
- 7: https://docs.github.com/en/rest/git/commits
🌐 Web query:
docs.github.com GitHub commit author email name can be spoofed raw commit fields
💡 Result:
Yes, it is possible to spoof the author and committer name and email address in a Git commit [1][2][3]. Because Git was designed in a distributed environment where email was the primary form of identity, it does not inherently validate that the metadata provided in a commit matches the actual person pushing the code [4][5]. When you push a commit to GitHub, the platform examines the email address recorded in the commit metadata [2][6]. If that email address is associated with a GitHub account, GitHub will display that account's name, profile picture, and profile link as the commit author or committer [2][7][3]. This can be performed easily using standard Git commands, such as: git config user.name "Name To Spoof" git config user.email "email@example.com" git commit --allow-empty -m "Spoofed commit message" Because this attribution happens automatically, it can be used to impersonate other developers, create misleading contribution graphs, or misrepresent the source of code in a repository [7][3][8]. To protect against this form of spoofing, GitHub provides a feature called Commit Signature Verification [5][9]. By cryptographically signing commits locally using GPG, SSH, or S/MIME keys, a developer provides a verifiable proof of identity [9][10]. When a commit is signed and the signature is verified, GitHub displays a green "Verified" badge [1][9]. Commits that are not signed do not receive this badge [9][10]. For environments requiring higher security, you can enable "Vigilant Mode" in your GitHub account settings under "SSH and GPG keys" [4]. Enabling this feature allows you to flag unsigned commits as "Unverified" with a warning in the user interface, providing a clear visual indicator that the commit's identity has not been cryptographically confirmed [4]. Top results: [1][7][4][9]
Citations:
- 1: https://www.gruntwork.io/blog/how-to-spoof-any-user-on-github-and-what-to-do-to-prevent-it
- 2: https://notes.kolkhis.dev/git/commit-metadata/
- 3: https://dwermke.com/pdf/conf-ndss-holtgrave25.pdf
- 4: https://sanj.dev/post/2025-12-09-git-commit-spoofing-is-trivial/
- 5: https://checkmarx.com/blog/unverified-commits-are-you-unknowingly-trusting-attackers-code/
- 6: https://repography.com/blog/how-to-take-credit-for-someone-elses-work-on-github
- 7: https://github.com/elsheraey/poc-commit-spoofing
- 8: https://capec.mitre.org/data/definitions/692.html
- 9: https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification
- 10: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
Use an exact trusted identity for the bot exclusion.
The rule currently skips any unsigned commit whose raw Git author name or email contains google-labs-jules. A contributor can set author metadata with that substring to bypass the check. Compare an exact trusted GitHub login or ID from .author/.committer, or an exact canonical bot email, and define whether the exception applies to the author, committer, or both.
🤖 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 @.github/workflows/commit.yml at line 27, Update the unsigned-commit filter
in the workflow’s unsigned_commits assignment to exclude only the trusted bot
identity via an exact match on the appropriate GitHub author/committer login or
ID, or canonical bot email; explicitly apply the exception to the author,
committer, or both, and remove substring checks against raw commit author name
or email.
Fixes Applied SuccessfullyFixed 3 file(s) based on 2 unresolved review comments. Files modified:
Commit: The changes have been pushed to the Time taken:
Lines 24–30 exit 1
fi
- unsigned_commits="$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$COMMITS_URL" | jq '.[] | select(.commit.verification.verified == false and (.commit.author.email | contains("google-labs-jules") | not) and (.commit.author.name | contains("google-labs-jules") | not)) | .commit.message')"
+ # Paginate through all commits (up to 250-commit limit for PR commits endpoint)
+ all_commits="[]"
+ page=1
+ while true; do
+ page_commits="$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$COMMITS_URL?page=$page&per_page=100")"
+ if [[ "$(echo "$page_commits" | jq 'length')" -eq 0 ]]; then
+ break
+ fi
+ all_commits="$(jq -s '.[0] + .[1]' <(echo "$all_commits") <(echo "$page_commits"))"
+ # GitHub's PR commits endpoint caps at 250 commits total
+ if [[ "$(echo "$all_commits" | jq 'length')" -ge 250 ]]; then
+ break
+ fi
+ page=$((page + 1))
+ done
+
+ unsigned_commits="$(echo "$all_commits" | jq '.[] | select(.commit.verification.verified == false and (.commit.author.email | contains("google-labs-jules") | not) and (.commit.author.name | contains("google-labs-jules") | not)) | .commit.message')"
if [[ -z "$unsigned_commits" ]]; then
echo "🎉 No unsigned commits found (excluding bot commits)"
exit 0 |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
desktop/src/main/ipc.ts (1)
231-257: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winTrack
closebefore skipping cleanup.This is a residual case in the earlier cancellation defect. The timeout sets
settledtotrue. If the child exits but a descendant retains its stdout or stderr pipe,exitCodecan be set whilecloseis still pending. Lines 257-283 then skip suppression and stream cleanup.workspace_deletecan start while lateonLinecallbacks still append to the removed log.Set a separate
closedflag only in the"close"handler. Run suppression and stream cleanup whenever the timeout resolves without observing"close".Proposed fix
- let settled = false + let closed = false const tunnelExit = new Promise<void>((resolve) => { let timer: ReturnType<typeof setTimeout> | null = setTimeout(() => { timer = null - settled = true resolve() }, 2000) - if (tunnelProc.exitCode !== null || tunnelProc.signalCode !== null) { - if (timer) clearTimeout(timer) - settled = true - resolve() - return - } tunnelProc.once("close", () => { if (timer) { clearTimeout(timer) timer = null } - settled = true + closed = true resolve() }) }) tunnelProc.kill("SIGTERM") await tunnelExit - if (!settled || (tunnelProc.exitCode === null && tunnelProc.signalCode === null)) { + if (!closed) {Also applies to: 1024-1030, 1077-1077, 1090-1093
🤖 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 231 - 257, Update the tunnel shutdown flow around tunnelExit to track a separate closed flag that is set only by the tunnelProc "close" handler, rather than using settled to represent process closure. After awaiting tunnelExit, run late-callback suppression and stdout/stderr stream cleanup whenever closed is false, including cases where exitCode or signalCode is already set; apply the same close-aware logic to the related cleanup paths.
🤖 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/cli.ts`:
- Around line 408-409: The force-kill timeout cleanup must run lifecycle
handlers before suppressing callbacks. In desktop/src/main/cli.ts lines 408-409,
invoke finish(...) for the CLI child, clear its sessionsByWorkspace entry, and
delete this.sessions before removing listeners and sending SIGKILL; in
desktop/src/main/pty.ts lines 129-130, send terminal:exit for the PTY child
before listener removal and SIGKILL.
---
Duplicate comments:
In `@desktop/src/main/ipc.ts`:
- Around line 231-257: Update the tunnel shutdown flow around tunnelExit to
track a separate closed flag that is set only by the tunnelProc "close" handler,
rather than using settled to represent process closure. After awaiting
tunnelExit, run late-callback suppression and stdout/stderr stream cleanup
whenever closed is false, including cases where exitCode or signalCode is
already set; apply the same close-aware logic to the related cleanup paths.
🪄 Autofix
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c6b28851-b9cb-44fb-a753-dafee3a6839b
📒 Files selected for processing (3)
desktop/src/main/cli.tsdesktop/src/main/ipc.tsdesktop/src/main/pty.ts
Fixes Applied SuccessfullyFixed 2 file(s) based on 1 unresolved review comment. Files modified:
Commit: The changes have been pushed to the Time taken: |
3bbd39d to
5790eae
Compare
Signed-off-by: GitHub <noreply@github.com>
5790eae to
b90db8b
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 `@desktop/src/main/cli.ts`:
- Around line 364-393: Separate timeout state from the promise’s completion
state in the child-wait logic: add a timedOut flag set by the 2-second timer,
then update the post-resolution live-child check to call finish(-1, { code:
"timeout", message: "Process did not exit in time" }) when timedOut is true and
the child has not exited. Keep settled for suppressing duplicate cleanup and
preserve normal close/exit handling.
🪄 Autofix
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4b7a1e10-0fa7-40c2-882f-80d86084f091
📒 Files selected for processing (3)
Taskfile.ymldesktop/src/main/cli.tsdesktop/src/main/pty.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- Taskfile.yml
- desktop/src/main/pty.ts
Fixes Applied SuccessfullyFixed 1 file(s) based on 1 unresolved review comment. Files modified:
Commit: The changes have been pushed to the Time taken: |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 61 |
| Duplication | 5 |
AI Reviewer: run a review on demand. To trigger the first review automatically, go to your organization or repository integration settings. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
The PR successfully implements the core workspace lifecycle UI and state management features. However, a critical logic error and scope issue in the CLI runner (cli.ts) will cause ReferenceError crashes and resource deadlocks during process timeouts, directly undermining the reliability goals of the PR.
While Codacy marks the PR as 'up to standards', there are significant maintainability concerns. desktop/src/main/ipc.ts has grown to over 1000 lines and is listed as an uncovered complex file, increasing the risk of regression. Additionally, while the UI components implement manual rollback logic for failed actions, there are no tests ensuring this behavior functions correctly, leaving a gap in the acceptance criteria fulfillment.
About this PR
- The forceful process termination logic (SIGKILL after 2s) is a critical safety feature to prevent orphaned processes and log file locks, but the timeout-triggered code path is not exercised by any tests in the diff.
- The logic for restoring workspace status on failure is implemented manually across multiple UI components (WorkspaceDetailPage and WorkspacesPage) but lacks dedicated unit or E2E tests to ensure the rollback behavior works as expected.
2 comments outside of the diff
desktop/src/main/cli.ts
line 58🟡 MEDIUM RISK
TheextractCliErrorFromStderrfunction is 271 lines long. Managing many different error patterns in a single function makes it difficult to maintain and test individual cases. Consider refactoring this to use a data-driven approach with a registry of regex-based error matchers.
desktop/src/main/ipc.ts
line 1🟡 MEDIUM RISK
This file has reached 1077 lines, which significantly exceeds the recommended limit of 500. It currently handles a wide range of responsibilities including workspace lifecycle, logging, SSH tunneling, and status tracking. Breaking this into smaller, domain-specific modules (e.g.,workspaces.ipc.ts,logs.ipc.ts) would reduce the risk of merge conflicts.
Test suggestions
- Verify DaemonState preserves existing workspace status when an update omits it
- Verify the frontend Svelte store merges and preserves workspace statuses during sync
- E2E: Perform a Start/Stop playthrough on a workspace and verify transitions to 'Starting', 'Running', 'Stopping', and 'Stopped'
- E2E: Verify Rebuild and Delete confirmation dialogs appear correctly
- Verify that a workspace status reverts to its previous state if an IPC action (e.g., handleStart) fails
- Verify CLI/PTY processes are forcefully terminated (SIGKILL) after the 2-second timeout in cancelFor
- Implement unit tests for logic in
desktop/src/main/ipc.tsto address complexity and lack of coverage - Implement unit tests for logic in
desktop/src/main/cli.tsto address complexity and lack of coverage - Implement unit tests for logic in
desktop/src/renderer/src/lib/ipc/commands.tsto address complexity and lack of coverage - Implement unit tests for logic in
desktop/src/main/pty.tsto address complexity and lack of coverage
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify that a workspace status reverts to its previous state if an IPC action (e.g., handleStart) fails
2. Verify CLI/PTY processes are forcefully terminated (SIGKILL) after the 2-second timeout in cancelFor
3. Implement unit tests for logic in `desktop/src/main/ipc.ts` to address complexity and lack of coverage
4. Implement unit tests for logic in `desktop/src/main/cli.ts` to address complexity and lack of coverage
5. Implement unit tests for logic in `desktop/src/renderer/src/lib/ipc/commands.ts` to address complexity and lack of coverage
6. Implement unit tests for logic in `desktop/src/main/pty.ts` to address complexity and lack of coverage
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| // so finish(...) can properly clean up sessions and call onExit | ||
| const alreadySettled = settled | ||
| if (!alreadySettled) { | ||
| finish(-1, { code: "timeout", message: "Process did not exit in time" }) |
There was a problem hiding this comment.
🔴 HIGH RISK
The finish function is not in scope here (causing a ReferenceError), and the surrounding conditional logic makes it unreachable even if it were. This will cause the application to leak CLI execution slots on process timeouts. Furthermore, this complex cleanup logic is duplicated across cli.ts, ipc.ts, and pty.ts. Centralizing this into a forceKillWithCleanup utility would resolve the bug and improve maintainability.
| @@ -908,6 +953,7 @@ export function registerIpcHandlers(deps: IpcDependencies): { | |||
| prebuildRepository?: string | |||
There was a problem hiding this comment.
🟡 MEDIUM RISK
The parameter interface for workspace_up is duplicated here and in desktop/src/renderer/src/lib/ipc/commands.ts. Extracting shared IPC contracts to a central types file would make the contract less brittle.
| tunnelProc.kill("SIGTERM") | ||
| await tunnelExit | ||
| // If process did not close in time, forcefully kill and suppress any late callbacks | ||
| if (!settled || (tunnelProc.exitCode === null && tunnelProc.signalCode === null)) { |
There was a problem hiding this comment.
⚪ LOW RISK
Nitpick: The !settled check is redundant because the promise only resolves after setting settled to true.
Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Signed-off-by: GitHub <noreply@github.com>
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/cli.ts (1)
365-392: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winWait for
closebefore resolving cancellation.
exitCodeandsignalCodecan be set beforeclose.runStreamingcallsfinishfrom theclosehandler, so resolving from lines 376-380 can letcancelForreturn while the child stays tracked and stdout/stderr can still emit data. Apply the same rule to the cleanup path after timeout resolves.🤖 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/cli.ts` around lines 365 - 392, Ensure the Promise in the cancellation wait resolves only from the child’s close event, not merely when exitCode or signalCode becomes non-null. Update the early-exit branch to wait for close while still clearing the timer, and preserve the same close-waiting behavior in the post-timeout cleanup before cancelFor returns.
🤖 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/cli.ts`:
- Around line 365-392: Ensure the Promise in the cancellation wait resolves only
from the child’s close event, not merely when exitCode or signalCode becomes
non-null. Update the early-exit branch to wait for close while still clearing
the timer, and preserve the same close-waiting behavior in the post-timeout
cleanup before cancelFor returns.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 02e096e5-0d52-42cc-946d-0aba579f1671
📒 Files selected for processing (3)
desktop/src/main/cli.tsdesktop/src/main/pty.tsdesktop/src/renderer/src/pages/WorkspaceDetailPage.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
- desktop/src/renderer/src/pages/WorkspaceDetailPage.svelte
- desktop/src/main/pty.ts
Signed-off-by: GitHub noreply@github.com
Summary by CodeRabbit
New Features
Bug Fixes
Chores