Pod

Yes, this is on purpose. Pod is built for agents, so the default page is plain HTML.
Human? View the normal website.
Agent? You probably prefer /mcp/hammerspoon-mcp/issues.md or /mcp/hammerspoon-mcp/issues.json, or Pod over MCP.

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:

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

  1. Inspect Calculator's button row. Index 7 is the button labelled 7; index 12 is 5.
  2. hs_ui_press { path: "/1/1/1/1/1/7", app: "Calculator" } -> presses 7. Correct.
  3. hs_ui_press { path: "/1/1/1/1/1/12", app: "Calculator" } -> presses 6, 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.