Skip to content

feat(profile): refuse writes while Elgato app is running + restart_app path fix#16

Merged
jack-arturo merged 4 commits into
mainfrom
feat/app-lifecycle-guard
Apr 17, 2026
Merged

feat(profile): refuse writes while Elgato app is running + restart_app path fix#16
jack-arturo merged 4 commits into
mainfrom
feat/app-lifecycle-guard

Conversation

@jack-arturo
Copy link
Copy Markdown
Member

Summary

Follow-up to #15. Closes a silent data-loss hole and fixes a pre-existing bug in the app-restart helper — both surfaced by the Phase 1 on-device smoke test.

  • Data loss bug: the Elgato desktop app caches every profile in memory and rewrites the on-disk manifests from its snapshot on quit. Edits made via streamdeck_write_page while the app is running are silently wiped the next time it closes (observed live during Phase 1 testing). This PR makes `write_page` refuse to run while the app is up and exposes a single additive `auto_quit_app` opt-in field for callers that want the tool to quit it for them (AppleScript → killall fallback).
  • restart_app -600 bug: `open -a "Stream Deck"` / `open -a "Elgato Stream Deck"` both fail with `LSOpenURLsWithCompletionHandler -600` on some macOS installs. Launching by explicit `/Applications/Elgato Stream Deck.app` path bypasses LaunchServices entirely. `STREAMDECK_APP_PATH` env var overrides the default for non-standard installs.
  • Tool count unchanged (still 6 tools). The new behavior is a single `auto_quit_app: bool = false` field on the existing `streamdeck_write_page` tool — per Jack's "avoid tool bloat" direction.

Workflow (added to README)

  1. `streamdeck_write_page(auto_quit_app: true, …)` — tool quits the app, writes, leaves app quit
  2. Additional writes can follow without re-passing `auto_quit_app` (app stays quit)
  3. `streamdeck_restart_app()` once done → device picks up the changes

Test plan

  • `uv run pytest tests/` — 44 passed (5 new)
  • `uv run ruff check .`
  • `uv run ruff format --check .`
  • Manual: reproduce the Phase 1 scenario end-to-end (app running → write fails with clear error → retry with auto_quit_app=true → write succeeds → restart_app shows the button on the deck)

Dependencies

Orthogonal to #15 — no merge-order requirement. Either can land first.

🤖 Generated with Claude Code

The Elgato Stream Deck app keeps every profile in memory and rewrites the
on-disk manifests from its snapshot when it quits. Any edit made while the
app is running is silently discarded the next time the app closes, which
is exactly what bit a live-hardware smoke test of the previous PR.

- Raise a new StreamDeckAppRunningError from write_page when the app is
  running, with a remediation message pointing callers at the auto_quit_app
  opt-in and the follow-up restart_app call.
- Add auto_quit_app=False param on write_page. When True and the app is
  running, quit it cleanly (AppleScript quit first, killall fallback)
  before writing; the report is surfaced in the write_page response.
- Rework restart_app to launch by explicit bundle path. The old
  `open -a "Stream Deck"` / `open -a "Elgato Stream Deck"` name lookup
  returns LSOpen error -600 on some macOS installs; launching by path
  bypasses LaunchServices. STREAMDECK_APP_PATH env var overrides the
  default `/Applications/Elgato Stream Deck.app`.
- Expose reusable is_stream_deck_app_running() and stop_stream_deck_app()
  helpers so other future entrypoints can share the same lifecycle logic.
- Update the README workflow section to spell out the quit → write →
  relaunch cycle and the env-var escape hatch.
- New tests (5) cover the refuse/auto-quit branches, the restart-by-path
  path, and the missing-bundle error. An autouse fixture stubs the
  process check so existing tests are unaffected regardless of whether
  the contributor has the app running locally.

Scope: unchanged tool count (6). auto_quit_app is a single additive field
on the existing write_page tool.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 17, 2026 18:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds safeguards to prevent Stream Deck profile writes from being silently lost while the Elgato desktop app is running, and fixes streamdeck_restart_app to relaunch via an explicit .app path (with an env-var override) to avoid macOS LaunchServices -600 failures.

Changes:

  • Refuse write_page when the Stream Deck app is running unless callers opt in via auto_quit_app=True (and surface a dedicated error type).
  • Add auto_quit_app to the MCP tool schema and server call path.
  • Rework restart_app to open an explicit app bundle path with STREAMDECK_APP_PATH override; document the new workflow in the README; add tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
profile_manager.py Adds app-running detection/stop helpers, blocks writes while app is running, and changes app restart logic to launch by path.
profile_server.py Extends streamdeck_write_page schema with auto_quit_app and returns a dedicated warning message for the new running-app error.
tests/test_profile_manager.py Adds tests for the running-app refusal, auto-quit path, and explicit restart-app path behavior.
README.md Documents the required quit → write → restart workflow and the STREAMDECK_APP_PATH override.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread profile_manager.py
Comment thread profile_manager.py Outdated
Comment thread profile_manager.py Outdated
Comment thread tests/test_profile_manager.py
@jack-arturo
Copy link
Copy Markdown
Member Author

@copilot apply changes based on the comments in this thread

Copilot AI and others added 2 commits April 17, 2026 18:56
…fore write, validate path before stop

Agent-Logs-Url: https://github.com/verygoodplugins/streamdeck-mcp/sessions/bf3b8260-ea35-455c-99cf-579444999ff4

Co-authored-by: jack-arturo <13076544+jack-arturo@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 17, 2026 18:57
@jack-arturo jack-arturo review requested due to automatic review settings April 17, 2026 18:57
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 17, 2026

@copilot apply changes based on the comments in this thread

Applied all four review changes in commit 3bbc9e8 (with a follow-up in 1fce5e8):

  1. Consistent stop_stream_deck_app() schema — early returns (non-darwin, not running) now always include graceful: [], forced: [], and reason.
  2. Verify app actually stopped before writingwrite_page() now checks both app_stop_report["stopped"] and a fresh is_stream_deck_app_running() call after stopping; raises StreamDeckAppRunningError with the report's reason if either indicates the app is still up.
  3. Validate app path before stopping in restart_app() — path resolution and existence check now happen before stop_stream_deck_app(), so a misconfigured path raises without orphaning the user.
  4. New test for stop-failure pathtest_write_page_auto_quit_app_raises_when_stop_fails covers stopped=False; the existing happy-path test updated to use side_effect=[True, False] so the post-stop is_stream_deck_app_running() call returns False.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • releases.astral.sh
    • Triggering command: /home/REDACTED/.local/bin/uv uv venv .venv (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@jack-arturo
Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Co-authored-by: jack-arturo <13076544+jack-arturo@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 17, 2026 19:27
@jack-arturo jack-arturo review requested due to automatic review settings April 17, 2026 19:27
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 17, 2026

@copilot resolve the merge conflicts in this pull request

Resolved in commit d3b831e. The conflict was in tests/test_profile_manager.py — main had added the sample_profiles_plus_xl fixture and encoder controller tests from #15 in the same region where this branch added the app-lifecycle tests. The merge keeps all tests from both branches (51 total, all passing).

@jack-arturo jack-arturo merged commit e1cb4e3 into main Apr 17, 2026
jack-arturo added a commit that referenced this pull request Apr 28, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.0](v0.2.0...v0.3.0)
(2026-04-28)


### Features

* **icons:** bundle Material Design Icons for offline glyph rendering
([#19](#19))
([459d410](459d410))
* **profile:** preserve encoder controller + Stream Deck + XL layout
([#15](#15))
([f4a8402](f4a8402))
* **profile:** refuse writes while Elgato app is running + restart_app
path fix
([#16](#16))
([e1cb4e3](e1cb4e3))
* **skill:** streamdeck-designer Agent Skill + MCP transport hardening
([#22](#22))
([fd3ec6f](fd3ec6f))
* **touchstrip:** expose $X1/$A0/$A1/$B1/$B2/$C1 encoder layouts
([#21](#21))
([efa66e4](efa66e4))
* **touchstrip:** per-segment icon and background on Stream Deck + / +
XL ([#20](#20))
([a41a31c](a41a31c))


### Bug Fixes

* add Stream Deck XL and missing device layouts
([#10](#10))
([c89435b](c89435b))
* **profile:** correct Stream Deck + XL keypad layout to 9x4
([#18](#18))
([d6da4b2](d6da4b2))
* **profile:** map 20GBD9901 to Stream Deck + (4 dials + 4x2 keypad)
([#25](#25))
([da0ada5](da0ada5))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

3 participants