Skip to content

fix: guard desktop_state null crash in switch_app/resize_app; fix click default#282

Closed
FelixIsaac wants to merge 1 commit into
CursorTouch:mainfrom
FelixIsaac:felix/fix-desktop-state-null-crash
Closed

fix: guard desktop_state null crash in switch_app/resize_app; fix click default#282
FelixIsaac wants to merge 1 commit into
CursorTouch:mainfrom
FelixIsaac:felix/fix-desktop-state-null-crash

Conversation

@FelixIsaac

Copy link
Copy Markdown

Summary

  • Crash bug: switch_app and resize_app both access self.desktop_state without checking if it has been populated. desktop_state is initialised to None in Desktop.__init__ and only set after get_state() is called. Calling App-Tool with mode=switch or mode=resize before State-Tool raises AttributeError: 'NoneType' object has no attribute 'active_app', which hangs the stdio MCP transport and causes all subsequent tool calls to time out indefinitely.
  • Default mismatch: Desktop.click() in service.py defaulted to clicks=2 (double-click) while the Click-Tool definition in main.py exposes clicks=1. Aligned both to single-click.

Changes

  • resize_app: call self.get_state() if self.desktop_state is None before accessing it
  • switch_app: same guard
  • Desktop.click(): change default clicks=2clicks=1

Test plan

  • Call App-Tool(mode=switch, name=...) without a prior State-Tool call — should switch window cleanly instead of hanging
  • Call App-Tool(mode=resize) without a prior State-Tool call — should return "No active app found" or resize cleanly
  • Click-Tool with default clicks param produces a single click, not a double-click

🤖 Generated with Claude Code

…ault

- switch_app and resize_app both access self.desktop_state without
  checking if it's been populated. desktop_state is None at init and
  only set after get_state() is called. Calling App-Tool(switch) or
  App-Tool(resize) before State-Tool crashes with AttributeError,
  hanging the stdio transport and timing out all subsequent tool calls.
- click() default clicks=2 (double-click) disagreed with the Click-Tool
  definition in main.py which exposes default=1. Align to single-click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (4)

Context used
✅ Compliance rules (platform): 15 rules

Grey Divider


Action required

1. resize_app missing docstring 📘 Rule violation ✧ Quality ⭐ New
Description
resize_app and switch_app are public methods whose first statement is now the added `if
self.desktop_state is None:` guard, so neither begins with a Google-style docstring as required.
This violates the documentation requirement for public functions, reducing API clarity,
maintainability, and readability for tool/service consumers.
Code

src/desktop/service.py[R142-143]

+        if self.desktop_state is None:
+            self.get_state()
Evidence
PR Compliance ID 222802 requires public functions to start with a Google-style docstring as the
first statement. In src/desktop/service.py, both resize_app and switch_app now have `if
self.desktop_state is None:` as the first executable line in their bodies, and there is no docstring
preceding that guard in either function, demonstrating the non-compliance.

Rule 222802: Require Google-style docstrings on public functions and classes
src/desktop/service.py[142-143]
src/desktop/service.py[213-214]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`resize_app` and `switch_app` are public methods but do not have a Google-style docstring as the first statement in the function body because a `desktop_state` guard was added above where a docstring should be.

## Issue Context
This PR introduced an `if self.desktop_state is None:` guard at the top of both `resize_app` and `switch_app`, making the first statement a non-docstring line and leaving no Google-style docstring preceding it, which violates PR Compliance ID 222802.

## Fix Focus Areas
- src/desktop/service.py[141-146]
- src/desktop/service.py[212-217]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. get_state remove may crash 🐞 Bug ☼ Reliability ⭐ New
Description
resize_app()/switch_app() now call get_state() when desktop_state is None, but
get_state() builds apps from one get_apps() call and active_app from get_active_app()
which calls get_apps() again; if those snapshots differ, apps.remove(active_app) can raise
ValueError and crash the call. Because App is a dataclass, equality includes fields like depth
and size, making this mismatch plausible even when the window handle is the same.
Code

src/desktop/service.py[R142-144]

+        if self.desktop_state is None:
+            self.get_state()
        active_app=self.desktop_state.active_app
Evidence
The new guard makes resize_app() invoke get_state(). get_state() constructs apps and
active_app from different get_apps() calls, then uses apps.remove(active_app). Since App is
a dataclass, equality compares all fields, so a small snapshot difference can make remove() fail
and raise ValueError.

src/desktop/service.py[141-146]
src/desktop/service.py[52-60]
src/desktop/service.py[79-88]
src/desktop/views.py[20-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`resize_app()` and `switch_app()` now call `self.get_state()` when `self.desktop_state` is `None`. However, `get_state()` can still raise `ValueError` at `apps.remove(active_app)` because `apps` and `active_app` are computed from two separate `get_apps()` snapshots, and `App` is a dataclass so equality compares all fields (including `depth`/`size`). This can still crash the tool call even after the new null guard.

## Issue Context
- `get_state()` calls `get_apps()` and then `get_active_app()`, and `get_active_app()` calls `get_apps()` again.
- The object returned by the second `get_apps()` call may not compare equal to the object in the first list (e.g., `depth` changes if enumeration order changes), so `apps.remove(active_app)` can fail.

## Fix Focus Areas
- src/desktop/service.py[52-60]
- src/desktop/service.py[79-88]

## Suggested fix
- Avoid calling `get_apps()` twice inside `get_state()`.
 - Option A (preferred): compute the foreground window handle once, then pick `active_app` from the already-fetched `apps` list by matching `handle`, and filter `apps` by `handle` rather than `remove()` by full-object equality.
 - Option B: change `get_active_app()` to accept a precomputed `apps` list (single snapshot) and return the matching object from that list.
- (Optional hardening) If `get_state()` fails, catch exceptions in `resize_app()`/`switch_app()` and return an error tuple instead of raising.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Stale active_app used 🐞 Bug ≡ Correctness
Description
After a successful switch_app(), self.desktop_state is not refreshed/updated, so a subsequent
resize_app() can read a stale desktop_state.active_app and resize the previously-focused window
instead of the current one. This is enabled by the new lazy initialization pattern (App-Tool can now
be called without State-Tool), making this incorrect behavior easier to hit in normal flows.
Code

src/desktop/service.py[R142-145]

+        if self.desktop_state is None:
+            self.get_state()
        active_app=self.desktop_state.active_app
        if active_app is None:
Evidence
get_state() is the only writer of self.desktop_state, while switch_app() does not update it
after switching; resize_app() then uses self.desktop_state.active_app as the resize target, so
it can be stale after switching.

src/desktop/service.py[52-70]
src/desktop/service.py[141-165]
src/desktop/service.py[212-230]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`switch_app()` changes the foreground window but does not update `self.desktop_state`. Because `resize_app()` uses `self.desktop_state.active_app` (and now lazily initializes state only once), consecutive `App-Tool` calls like `switch` -> `resize` can act on the wrong window.

### Issue Context
- `Desktop.get_state()` is the only place that populates `self.desktop_state`.
- `switch_app()` returns success without updating `desktop_state.active_app`.
- `resize_app()` reads `desktop_state.active_app` and resizes that handle.

### Fix Focus Areas
- src/desktop/service.py[141-165]
- src/desktop/service.py[212-230]
- src/desktop/service.py[52-70]

### Suggested fix
- After a successful `switch_app()` (restore/bring-to-top), either:
 1) Update `self.desktop_state.active_app` to the switched-to `app` and remove it from `self.desktop_state.apps` if present, **or**
 2) Call a lightweight refresh that at least recomputes `active_app` (avoid full tree scan if possible).
- In `resize_app()`, consider fetching the current active app via `get_active_app()` (or refreshing when `desktop_state.active_app` is missing/outdated) so it targets the actual foreground window.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. click() missing docstring 📘 Rule violation ✧ Quality
Description
The modified public method click() has no Google-style docstring as the first statement in the
function body. This reduces API clarity and violates the documentation requirement for public
functions.
Code

src/desktop/service.py[R257-259]

+    def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):
        x,y=loc
        pg.click(x,y,button=button,clicks=clicks,duration=0.1)
Evidence
PR Compliance ID 222802 requires Google-style docstrings on public functions. In the modified
click() method, the first statement is x,y=loc rather than a triple-quoted docstring, indicating
the required docstring is missing.

Rule 222802: Require Google-style docstrings on public functions and classes
src/desktop/service.py[257-259]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The public `click()` method is missing a Google-style docstring (triple-quoted string as the first statement) describing its purpose, args, and behavior.

## Issue Context
This method signature was modified in this PR, so it must meet the docstring requirement for public functions.

## Fix Focus Areas
- src/desktop/service.py[257-259]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. click() uses 'left' 📘 Rule violation ✧ Quality
Description
The updated click() signature sets the default button value using single quotes ('left'). This
violates the requirement that all string literals use double quotes, which helps keep string style
consistent across the codebase.
Code

src/desktop/service.py[257]

+    def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):
Evidence
PR Compliance ID 222799 requires double quotes for all string literals. The modified click()
signature uses a single-quoted literal ('left') as a default parameter value.

Rule 222799: Enforce double quotes for all string literals
src/desktop/service.py[257-257]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A modified line introduces/retains a single-quoted string literal (`'left'`) in `click()`'s default argument, but compliance requires double quotes for all string literals.

## Issue Context
This is in a function signature line changed in this PR, so it must comply with the string-literal quoting rule.

## Fix Focus Areas
- src/desktop/service.py[257-257]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. click() missing return type 📘 Rule violation ✧ Quality
Description
The modified click() method lacks an explicit return type annotation. This violates the
requirement that all function signatures include return type hints, reducing static checkability.
Code

src/desktop/service.py[257]

+    def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):
Evidence
PR Compliance ID 222805 requires explicit type hints for all parameters and return types on modified
function signatures. The changed click() definition has parameter annotations but no -> ...
return annotation.

Rule 222805: Require type hints for all function signatures
src/desktop/service.py[257-259]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `click()` method signature is missing an explicit return type annotation.

## Issue Context
Per compliance, all function/method signatures must include parameter and return type hints; `click()` appears to return nothing and should be annotated as `-> None`.

## Fix Focus Areas
- src/desktop/service.py[257-259]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. Heavy init for switch/resize 🐞 Bug ➹ Performance
Description
The new desktop_state is None guard calls get_state(), which always performs a full UI tree scan
via Tree.get_state() even though switch/resize only require the app/window list and active window.
This increases latency for the first switch_app/resize_app call and can be avoided by using a
lightweight state initializer.
Code

src/desktop/service.py[R213-214]

+        if self.desktop_state is None:
+            self.get_state()
Evidence
The newly-added guard calls get_state(), and get_state() always computes tree_state by calling
into Tree.get_state(), which performs concurrent UIAutomation enumeration, making it a
heavier-than-needed initialization for app switching/resizing.

src/desktop/service.py[52-70]
src/desktop/service.py[141-146]
src/tree/service.py[33-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`switch_app()`/`resize_app()` now initialize `desktop_state` by calling `get_state()`. `get_state()` always computes `tree_state` via a UIAutomation traversal, which is unnecessary overhead for operations that only need `apps`/`active_app`.

### Issue Context
- `Desktop.get_state()` always calls `self.tree.get_state(...)`.
- `Tree.get_state()` triggers concurrent node enumeration (`ThreadPoolExecutor`) across windows.

### Fix Focus Areas
- src/desktop/service.py[52-70]
- src/desktop/service.py[141-146]
- src/desktop/service.py[212-216]
- src/tree/service.py[33-71]

### Suggested fix
- Add a lightweight initializer, e.g. `get_apps_state()` that sets `self.desktop_state = DesktopState(apps=..., active_app=..., screenshot=None, tree_state=TreeState())` (empty `TreeState`), or add a flag to `get_state(include_tree: bool = True)`.
- Use that lightweight path in `switch_app()`/`resize_app()` when the caller hasn’t requested UI element enumeration/screenshot.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit c20bbfb

Results up to commit c20bbfb


🐞 Bugs (2) 📘 Rule violations (3) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)


Action required
1. click() uses 'left' 📘 Rule violation ✧ Quality
Description
The updated click() signature sets the default button value using single quotes ('left'). This
violates the requirement that all string literals use double quotes, which helps keep string style
consistent across the codebase.
Code

src/desktop/service.py[257]

+    def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):
Evidence
PR Compliance ID 222799 requires double quotes for all string literals. The modified click()
signature uses a single-quoted literal ('left') as a default parameter value.

Rule 222799: Enforce double quotes for all string literals
src/desktop/service.py[257-257]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A modified line introduces/retains a single-quoted string literal (`'left'`) in `click()`'s default argument, but compliance requires double quotes for all string literals.

## Issue Context
This is in a function signature line changed in this PR, so it must comply with the string-literal quoting rule.

## Fix Focus Areas
- src/desktop/service.py[257-257]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. click() missing return type 📘 Rule violation ✧ Quality
Description
The modified click() method lacks an explicit return type annotation. This violates the
requirement that all function signatures include return type hints, reducing static checkability.
Code

src/desktop/service.py[257]

+    def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):
Evidence
PR Compliance ID 222805 requires explicit type hints for all parameters and return types on modified
function signatures. The changed click() definition has parameter annotations but no -> ...
return annotation.

Rule 222805: Require type hints for all function signatures
src/desktop/service.py[257-259]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `click()` method signature is missing an explicit return type annotation.

## Issue Context
Per compliance, all function/method signatures must include parameter and return type hints; `click()` appears to return nothing and should be annotated as `-> None`.

## Fix Focus Areas
- src/desktop/service.py[257-259]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. click() missing docstring 📘 Rule violation ✧ Quality
Description
The modified public method click() has no Google-style docstring as the first statement in the
function body. This reduces API clarity and violates the documentation requirement for public
functions.
Code

src/desktop/service.py[R257-259]

+    def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):
        x,y=loc
        pg.click(x,y,button=button,clicks=clicks,duration=0.1)
Evidence
PR Compliance ID 222802 requires Google-style docstrings on public functions. In the modified
click() method, the first statement is x,y=loc rather than a triple-quoted docstring, indicating
the required docstring is missing.

Rule 222802: Require Google-style docstrings on public functions and classes
src/desktop/service.py[257-259]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The public `click()` method is missing a Google-style docstring (triple-quoted string as the first statement) describing its purpose, args, and behavior.

## Issue Context
This method signature was modified in this PR, so it must meet the docstring requirement for public functions.

## Fix Focus Areas
- src/desktop/service.py[257-259]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Stale active_app used 🐞 Bug ≡ Correctness
Description
After a successful switch_app(), self.desktop_state is not refreshed/updated, so a subsequent
resize_app() can read a stale desktop_state.active_app and resize the previously-focused window
instead of the current one. This is enabled by the new lazy initialization pattern (App-Tool can now
be called without State-Tool), making this incorrect behavior easier to hit in normal flows.
Code

src/desktop/service.py[R142-145]

+        if self.desktop_state is None:
+            self.get_state()
        active_app=self.desktop_state.active_app
        if active_app is None:
Evidence
get_state() is the only writer of self.desktop_state, while switch_app() does not update it
after switching; resize_app() then uses self.desktop_state.active_app as the resize target, so
it can be stale after switching.

src/desktop/service.py[52-70]
src/desktop/service.py[141-165]
src/desktop/service.py[212-230]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`switch_app()` changes the foreground window but does not update `self.desktop_state`. Because `resize_app()` uses `self.desktop_state.active_app` (and now lazily initializes state only once), consecutive `App-Tool` calls like `switch` -> `resize` can act on the wrong window.

### Issue Context
- `Desktop.get_state()` is the only place that populates `self.desktop_state`.
- `switch_app()` returns success without updating `desktop_state.active_app`.
- `resize_app()` reads `desktop_state.active_app` and resizes that handle.

### Fix Focus Areas
- src/desktop/service.py[141-165]
- src/desktop/service.py[212-230]
- src/desktop/service.py[52-70]

### Suggested fix
- After a successful `switch_app()` (restore/bring-to-top), either:
 1) Update `self.desktop_state.active_app` to the switched-to `app` and remove it from `self.desktop_state.apps` if present, **or**
 2) Call a lightweight refresh that at least recomputes `active_app` (avoid full tree scan if possible).
- In `resize_app()`, consider fetching the current active app via `get_active_app()` (or refreshing when `desktop_state.active_app` is missing/outdated) so it targets the actual foreground window.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
5. Heavy init for switch/resize 🐞 Bug ➹ Performance
Description
The new desktop_state is None guard calls get_state(), which always performs a full UI tree scan
via Tree.get_state() even though switch/resize only require the app/window list and active window.
This increases latency for the first switch_app/resize_app call and can be avoided by using a
lightweight state initializer.
Code

src/desktop/service.py[R213-214]

+        if self.desktop_state is None:
+            self.get_state()
Evidence
The newly-added guard calls get_state(), and get_state() always computes tree_state by calling
into Tree.get_state(), which performs concurrent UIAutomation enumeration, making it a
heavier-than-needed initialization for app switching/resizing.

src/desktop/service.py[52-70]
src/desktop/service.py[141-146]
src/tree/service.py[33-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`switch_app()`/`resize_app()` now initialize `desktop_state` by calling `get_state()`. `get_state()` always computes `tree_state` via a UIAutomation traversal, which is unnecessary overhead for operations that only need `apps`/`active_app`.

### Issue Context
- `Desktop.get_state()` always calls `self.tree.get_state(...)`.
- `Tree.get_state()` triggers concurrent node enumeration (`ThreadPoolExecutor`) across windows.

### Fix Focus Areas
- src/desktop/service.py[52-70]
- src/desktop/service.py[141-146]
- src/desktop/service.py[212-216]
- src/tree/service.py[33-71]

### Suggested fix
- Add a lightweight initializer, e.g. `get_apps_state()` that sets `self.desktop_state = DesktopState(apps=..., active_app=..., screenshot=None, tree_state=TreeState())` (empty `TreeState`), or add a flag to `get_state(include_tree: bool = True)`.
- Use that lightweight path in `switch_app()`/`resize_app()` when the caller hasn’t requested UI element enumeration/screenshot.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Jun 13, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Guard uninitialized desktop_state in switch/resize; align click default to single
🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

Walkthroughs

Description
• Prevent App-Tool switch/resize from crashing when desktop_state is uninitialized.
• Lazily populate desktop_state via get_state() before accessing active_app/apps.
• Align Desktop.click() default clicks with Click-Tool default (single click).
Diagram
graph TD
  A["App-Tool"] --> B["Desktop.app()"] --> C["switch_app()/resize_app()"] --> D["get_state() if desktop_state None"] --> E["desktop_state"]
  F["Click-Tool"] --> G["Desktop.click()"] --> H["pyautogui.click()"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Centralize guard via ensure_state() helper
  • ➕ Avoids repeating the same None-check in each method that needs desktop_state
  • ➕ Makes future state-dependent methods harder to forget to guard
  • ➖ Slightly more refactor than the minimal change
  • ➖ Still relies on implicit state refresh timing semantics
2. Initialize desktop_state eagerly at startup
  • ➕ Eliminates an entire class of None dereference issues for state consumers
  • ➖ Adds startup cost and potentially slower server/tool initialization
  • ➖ May capture stale state if long-lived without refresh semantics

Recommendation: The PR’s lazy guard is the right minimal-risk fix for a crash that breaks the stdio transport. If additional methods start depending on desktop_state, consider introducing a small ensure_state() helper (or moving the guard into Desktop.app() for switch/resize paths) to keep the invariant in one place.

Grey Divider

File Changes

Bug fix (1)
service.py Lazy-initialize desktop_state for resize/switch; fix click default +5/-1

Lazy-initialize desktop_state for resize/switch; fix click default

• Adds a defensive guard in resize_app() and switch_app() to call get_state() when desktop_state is None, preventing AttributeError when tools are called out of order. Updates Desktop.click() default from double-click (2) to single-click (1) to match Click-Tool’s exposed default.

src/desktop/service.py


Grey Divider

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Qodo Logo

Comment thread src/desktop/service.py
return bounding_rectangle.xcenter(),bounding_rectangle.ycenter()

def click(self,loc:tuple[int,int],button:str='left',clicks:int=2):
def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. click() uses 'left' 📘 Rule violation ✧ Quality

The updated click() signature sets the default button value using single quotes ('left'). This
violates the requirement that all string literals use double quotes, which helps keep string style
consistent across the codebase.
Agent Prompt
## Issue description
A modified line introduces/retains a single-quoted string literal (`'left'`) in `click()`'s default argument, but compliance requires double quotes for all string literals.

## Issue Context
This is in a function signature line changed in this PR, so it must comply with the string-literal quoting rule.

## Fix Focus Areas
- src/desktop/service.py[257-257]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/desktop/service.py
return bounding_rectangle.xcenter(),bounding_rectangle.ycenter()

def click(self,loc:tuple[int,int],button:str='left',clicks:int=2):
def click(self,loc:tuple[int,int],button:str='left',clicks:int=1):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. click() missing return type 📘 Rule violation ✧ Quality

The modified click() method lacks an explicit return type annotation. This violates the
requirement that all function signatures include return type hints, reducing static checkability.
Agent Prompt
## Issue description
The `click()` method signature is missing an explicit return type annotation.

## Issue Context
Per compliance, all function/method signatures must include parameter and return type hints; `click()` appears to return nothing and should be annotated as `-> None`.

## Fix Focus Areas
- src/desktop/service.py[257-259]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/desktop/service.py
Comment thread src/desktop/service.py
Jeomon added a commit that referenced this pull request Jun 13, 2026
The Click-Tool definition exposed clicks=1 but Desktop.click() defaulted
to clicks=2, causing unexpected double-clicks when callers relied on the
default. Aligns the service layer to match the tool's documented behavior.

Resolves the click-default mismatch from PR #282; null-guard fixes for
resize_app/switch_app were already covered by the prior refactor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Jeomon Jeomon closed this Jun 13, 2026
@Jeomon Jeomon reopened this Jun 13, 2026
@qodo-code-review

qodo-code-review Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c20bbfb

Comment thread src/desktop/service.py
Comment on lines +142 to +143
if self.desktop_state is None:
self.get_state()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. resize_app missing docstring 📘 Rule violation ✧ Quality

resize_app and switch_app are public methods whose first statement is now the added `if
self.desktop_state is None:` guard, so neither begins with a Google-style docstring as required.
This violates the documentation requirement for public functions, reducing API clarity,
maintainability, and readability for tool/service consumers.
Agent Prompt
## Issue description
`resize_app` and `switch_app` are public methods but do not have a Google-style docstring as the first statement in the function body because a `desktop_state` guard was added above where a docstring should be.

## Issue Context
This PR introduced an `if self.desktop_state is None:` guard at the top of both `resize_app` and `switch_app`, making the first statement a non-docstring line and leaving no Google-style docstring preceding it, which violates PR Compliance ID 222802.

## Fix Focus Areas
- src/desktop/service.py[141-146]
- src/desktop/service.py[212-217]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/desktop/service.py
Comment on lines +142 to 144
if self.desktop_state is None:
self.get_state()
active_app=self.desktop_state.active_app

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Get_state remove may crash 🐞 Bug ☼ Reliability

resize_app()/switch_app() now call get_state() when desktop_state is None, but
get_state() builds apps from one get_apps() call and active_app from get_active_app()
which calls get_apps() again; if those snapshots differ, apps.remove(active_app) can raise
ValueError and crash the call. Because App is a dataclass, equality includes fields like depth
and size, making this mismatch plausible even when the window handle is the same.
Agent Prompt
## Issue description
`resize_app()` and `switch_app()` now call `self.get_state()` when `self.desktop_state` is `None`. However, `get_state()` can still raise `ValueError` at `apps.remove(active_app)` because `apps` and `active_app` are computed from two separate `get_apps()` snapshots, and `App` is a dataclass so equality compares all fields (including `depth`/`size`). This can still crash the tool call even after the new null guard.

## Issue Context
- `get_state()` calls `get_apps()` and then `get_active_app()`, and `get_active_app()` calls `get_apps()` again.
- The object returned by the second `get_apps()` call may not compare equal to the object in the first list (e.g., `depth` changes if enumeration order changes), so `apps.remove(active_app)` can fail.

## Fix Focus Areas
- src/desktop/service.py[52-60]
- src/desktop/service.py[79-88]

## Suggested fix
- Avoid calling `get_apps()` twice inside `get_state()`.
  - Option A (preferred): compute the foreground window handle once, then pick `active_app` from the already-fetched `apps` list by matching `handle`, and filter `apps` by `handle` rather than `remove()` by full-object equality.
  - Option B: change `get_active_app()` to accept a precomputed `apps` list (single snapshot) and return the matching object from that list.
- (Optional hardening) If `get_state()` fails, catch exceptions in `resize_app()`/`switch_app()` and return an error tuple instead of raising.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@Jeomon

Jeomon commented Jun 13, 2026

Copy link
Copy Markdown
Member

Thank you! All three fixes have been applied to main — the null guards for resize_app/switch_app were already covered by the refactor, and the click default has been fixed in 214fa63. Closing as resolved.

@Jeomon Jeomon closed this Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants