Skip to content

Fix stale get_accessory reads end to end - #116

Open
ankjaers wants to merge 1 commit into
omarshahine:mainfrom
ankjaers:fix/get-accessory-read-freshness
Open

ankjaers wants to merge 1 commit into
omarshahine:mainfrom
ankjaers:fix/get-accessory-read-freshness

Conversation

@ankjaers

Copy link
Copy Markdown

Problem

PR #70 added a hard timeout around HomeKit characteristic reads, but the resulting success/failure value is discarded before get_accessory is serialized.

When HMCharacteristic.readValue times out or returns an error, HMCharacteristic.value can still contain its previous value. The socket then returns that last-known value inside success: true, and neither the CLI nor the stdio MCP server can tell whether it was observed by the current request.

Changes

  • Capture a read attestation inside each HomeKit completion handler.
  • Add an additive get_accessory wire contract:
    • top-level refreshed
    • read_attempted and read_succeeded counts
    • per-readable-characteristic read.succeeded
    • completion-bound read.observed_at for successful reads only
  • Do not update the accessory cache from failed reads.
  • Make default CLI get fail nonzero for failed, missing, malformed, or internally inconsistent freshness metadata.
  • Apply the same fail-closed validation to the active stdio MCP get path.
  • Preserve stale output only when callers explicitly request --no-refresh / no_refresh: true.
  • Keep demo responses wire-compatible and add deterministic Swift and Node regression tests.
  • Always rebuild the MCP bundle during an Xcode build when Node dependencies are available, so imported lib/* changes cannot be skipped by an incomplete mtime check.

Example successful metadata:

{
  "refreshed": true,
  "read_attempted": 1,
  "read_succeeded": 1,
  "services": [
    {
      "characteristics": [
        {
          "read": {
            "succeeded": true,
            "observed_at": "2026-09-10T14:00:00.125Z"
          }
        }
      ]
    }
  ]
}

A failed read emits succeeded: false without an observation timestamp and makes the request-level refreshed value false.

Scope

This PR attests the request-oriented get_accessory path. Bulk cache-warming policy is intentionally unchanged and can be hardened independently without coupling this wire fix to cache-generation semantics.

Verification

  • swift test — 187 tests passed
  • npm ci — 0 vulnerabilities
  • npm run test:mcp — 3 tests passed
  • npm run build:mcp — passed
  • Unsigned Mac Catalyst HomeClaw build — passed with Xcode 26.6
  • Committed MCP bundle syntax (node --check) — passed

The automated Catalyst build is intentionally unsigned, matching this repository's CI. The freshness behavior uses deterministic payload tests rather than timing-based sleeps.

@ankjaers

Copy link
Copy Markdown
Author

👋
There is a real person behind this pr. Feedback and concerns is appreciated

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.

1 participant