Reported issues for hammerspoon-mcp
Pod holds 18 of 32 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.
Back to hammerspoon-mcp.
Most discussed
Fix tools hs_ui_press label chain diverges from hs_ui_inspect
Found reviewing the #27 follow-up (6a30a9e).
hs_ui_press builds the label it compares expectLabel against from three attributes. hs_ui_inspect builds the label it reports from five. Any element whose label comes from the last two can never satisfy the guard, so the press is refused every time, and the error tells the caller to re-inspect - which returns the same label.
Verified
src/tools/safe/accessibility.ts:61-67:
local function labelOf(element)
return attr(e
[Read the thread](https://github.com/vukvukovich/hammerspoon-mcp/issues/31) · 2026-08-14 · closed · 2 comments
### Fix bridge harden against hs CLI client-side crashes and IPC failure windows
### Context
Hammerspoon crashed during development on 2026-08-11 with `EXC_BREAKPOINT` in `ipc_sendMessage` -> `CFMessagePortSendRequest` -> `__CFCheckCFInfoPACSignature`, a pointer-authentication failure on an invalidated Mach port. The trigger appears to be `hs.reload()` running while another IPC call was in flight: the config was being saved (auto-reload) while an integration test run was hammering `hs -c`.
This is Hammerspoon fragility rather than a defect in this server, but `hs_reload_con
[Read the thread](https://github.com/vukvukovich/hammerspoon-mcp/issues/13) · 2026-08-11 · open · 2 comments
### Chr publish v0.1.0 to npm and register in MCP directories
### Context
Discovery is most of the value for a niche tool. The name `hammerspoon-mcp` is already taken on npm by another project, so this publishes under the scope `@vukvukovich/hammerspoon-mcp`. The GitHub repository and the MCP registry both namespace by owner, so the plain name is retained in those two places.
### Goal
An installable, discoverable v0.1.0 that a Hammerspoon user searching for MCP support will actually find.
### Acceptance criteria
- [ ] Version bumped in package.json, with
[Read the thread](https://github.com/vukvukovich/hammerspoon-mcp/issues/11) · 2026-08-11 · closed · 2 comments
### Opt bridge codec cleanups: normalize the unencodable flag once
Found in the second review pass over the #29 plumbing. Small cleanups in `src/bridge/codec.ts`, none load-bearing.
### The unencodable flag is normalized twice
`readEnvelope` coerces the flag to a mandatory boolean, then `envelopeToResult` re-coerces that boolean to optional-true via a two-branch ternary that duplicates `ok: true, value: envelope.value`. Two representations of one flag (`LuaEnvelope`'s `unencodable: boolean` vs `BridgeResult`'s `unencodable?: true`) and two conversion points t
[Read the thread](https://github.com/vukvukovich/hammerspoon-mcp/issues/37) · 2026-08-14 · closed · 1 comment
### Opt tools hs_ui_press verification should be structural, not advisory
Follow-up to #27 (`6a30a9e`). The identity guard works, but its guarantees are weaker than they read, in three ways. None is a bug in what the code does - each is a gap between what it does and what it appears to promise.
### 1. Verification is opt-in, enforced by prose
A press with neither `expectLabel` nor `expectRole` proceeds exactly as before, reporting `verified: false`. The only thing demanding an expectation is the ALL-CAPS instruction in the tool description - so the safety property h
[Read the thread](https://github.com/vukvukovich/hammerspoon-mcp/issues/36) · 2026-08-14 · closed · 1 comment
### Opt tools one vocabulary for values that cannot be represented
Found in the second review pass. Cleanup, not a defect - but it is one concept shipped as two wire shapes in a single release, and the next tool with the same problem will coin a third.
### The duplication
"This value could not be represented; here is its string form, plus a hint" exists twice:
- `src/tools/registry.ts:112` (fromBridge's unencodable branch): `{ value, encodable: false, hint }`
- `src/tools/unsafe/applescript.ts:64` (built in Lua): `{ raw, representable: false, hint }`
Differ
[Read the thread](https://github.com/vukvukovich/hammerspoon-mcp/issues/35) · 2026-08-14 · closed · 1 comment
### Fix bridge a throwing __tostring escapes the codec fallback and destroys the envelope
Found in the second review pass over `da5d14c..HEAD`. Pre-existing, but the #29 fix makes the codec's fallback path the sole producer of the `unencodable` flag, so its one hole now matters.
### Verified live
In `buildProgram` (`src/bridge/codec.ts:116`), the fallback encodes the tostring form:
```lua
local __ok2, __alt = pcall(hs.json.encode, { ok = true, value = tostring(__res), unencodable = true })
pcall protects hs.json.encode - but tostring(__res) is evaluated **while building
Read the thread · 2026-08-14 · closed · 1 comment
Tst hs_ui_press guard has no automatic coverage and its test is destructive
Found reviewing the #27 fix (6a30a9e). The guard that stops hs_ui_press clicking the wrong element is a security property, and nothing that runs automatically checks it.
No automatic coverage
grep -rn "expectLabel\|expectRole\|verified" test/unit/ returns nothing. The only coverage is test/integration/bridge.test.ts:640-720, and:
- the file is wrapped in
describe.skipIf(!available)(line 30) and its own header says these tests never run in CI npm testruns the unit project o
Read the thread · 2026-08-14 · closed · 1 comment
Most recent
Fix tools hs_applescript follow-ups: surrogate escapes, missing value, dead branch
Found reviewing the #28 fix (0f28b4a). Three defects in the new code, all verified live against Hammerspoon on macOS 26.
1. The \Uxxxx decoder splits surrogate pairs
readable() (src/tools/unsafe/applescript.ts:39-44) decodes each \Uxxxx escape independently. NSError descriptions are UTF-16, so any non-BMP character arrives as a surrogate pair of two escapes. Decoding each half separately produces two lone surrogates - CESU-8, not valid UTF-8.
Live, from a real error dictiona
Read the thread · 2026-08-14 · closed · 1 comment
Doc tools hs_eval returns only the first of several return values
Found in the gated-tier test pass. Minor, but it is a silent truncation.
return 1, 2, 3 yields 1. The other two are dropped without comment, because the codec wraps the body in pcall(function() ... end) and keeps a single result.
Lua's multiple returns are idiomatic (local ok, err = pcall(...), string.find returning two indexes), so an agent writing Lua will hit this. The tool's description says "Use `return` to produce a value", which is technically singular but easy to read past.
Read the thread · 2026-08-14 · closed · 1 comment
Fix bridge the unencodable flag is dropped before it reaches the caller
Found in the gated-tier test pass, but it affects every tool.
The wire protocol has three shapes, and ARCHITECTURE.md documents the third:
{ "ok": true, "value": "hs.window: Safari", "unencodable": true }
The Lua side produces it correctly. The TypeScript side then throws the flag away:
export function envelopeToResult(envelope, toLuaError) {
if (envelope.ok) return { ok: true, value: envelope.value }; // unencodable lost
...
}
LuaEnvelope even declares the fiel
Read the thread · 2026-08-14 · closed · 1 comment
Fix tools hs_applescript throws away every error detail
Found in the gated-tier test pass. Every AppleScript failure returns the bare string AppleScript failed, with nothing after the colon, so the caller learns only that something went wrong.
Observed for all of: syntax error, error "deliberate failure", undefined variable, division by zero, and a valid-looking record literal. Five different causes, one indistinguishable message.
Root cause, verified
hs.osascript.applescript(source) returns three values: ok, result, descriptor
Read the thread · 2026-08-14 · closed · 1 comment
Fix tools hs_ui_press presses the wrong element when indexes shift
Found in the gated-tier test pass. This is the most dangerous defect found so far: a destructive tool that presses something other than what the caller intended, and reports success.
Reproduction, on Calculator
- Inspect Calculator's button row. Index 7 is the button labelled
7; index 12 is5. hs_ui_press { path: "/1/1/1/1/1/7", app: "Calculator" }-> presses7. Correct.hs_ui_press { path: "/1/1/1/1/1/12", app: "Calculator" }-> presses6, returns `{ label: "6", ...
Read the thread · 2026-08-14 · closed · 1 comment
Opt tools poll instead of fixed settle sleeps and share one sleep helper
Found by code review of the v0.2.0 changes. Quality, not correctness.
1. hs_move_window pays worst-case latency on every call
The fixed 400ms settle runs before every read-back although the measured correction window starts at ~40ms and most moves are never adjusted. Poll the frame every ~100ms and return as soon as it matches the request (same early-exit pattern waitForSpace already uses in spaces.ts); unadjusted moves finish in ~100ms with the same 400ms worst case.
2. Hand-rolled sl
Read the thread · 2026-08-13 · closed · 1 comment
Tst restore the per-tool bridge call-count guard
Found by code review of the v0.2.0 changes.
Root cause
The 'runs a static Lua program' unit test was relaxed from exactly-one bridge call to at-least-one so the new read-back handlers (hs_move_window, hs_goto_space, hs_music_control) could pass — but the relaxation applies to every tool, so a single-call tool that regresses into issuing extra side-effecting programs per invocation (a double hs_launch_app, say) no longer fails anything.
Fix
A per-tool expected-call-count map: default 1,
Read the thread · 2026-08-13 · closed · 1 comment
Fix tools remaining output gaps: empty sub-tables, post-skip track, scheme case
Found by code review of the v0.2.0 changes. Follow-up to #18; three smaller correctness gaps.
1. hs_machine_status: battery and audio can still encode as []
The empty-table-encodes-as-[] fix was applied only to the network sub-table (by adding an always-present field). battery on a desktop Mac and audio when the device reads fail can still hold only nils and encode as a bare array. Fix the mechanism: every sub-table carries at least one always-present field (e.g. present = ... ~= nil).
Read the thread · 2026-08-13 · closed · 1 comment
Fix tools hs_reload_config anchor the reload timer against garbage collection
Found by code review of the v0.2.0 changes.
Root cause
RELOAD_LUA discards the hs.timer.doAfter return value. An unanchored Hammerspoon object can be garbage-collected before it fires, and this very PR documents that exact footgun for hs.task in shortcuts.ts (a collected started task is killed mid-run; a collected timer simply never fires). The 0.15s -> 0.5s delay change widened the window.
Failure scenario
hs_reload_config returns 'Reload scheduled', a GC cycle collects the timer insi
Read the thread · 2026-08-13 · closed · 1 comment
Fix tools hs_speak substring match uses short names and exact match hides duplicates
Found by code review of the v0.2.0 changes. Two holes in the new voice validation, both re-opening the silent-wrong-voice bug (#17) it was built to close.
1. Single substring match resolves to the short name
The unambiguous-substring path sets voiceName = matches[1], the short-list entry, while the exact-match path deliberately maps to the full identifier because hs.speech.new silently falls back to the default voice for names it cannot resolve. 'Dani' -> 'Daniel' -> hs.speech.new('Daniel
Read the thread · 2026-08-13 · closed · 1 comment
The remaining reports are on the project's issue tracker.