From 9e940859a39100f2038b60e174f6afd0d99baebf Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Sat, 18 Apr 2026 15:31:53 +0100 Subject: [PATCH 1/2] add /indigo:debug-sqllogger skill + command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Walks through debugging which Indigo devices are causing the generic "SQL Logger Error: One or more failures updating device history" spam. Promotes the two swallowed logger.debug(..., exc_info=True) calls to error-level via a [DEBUG-PATCH]-tagged edit, extracts the failing device_history_ + exception, resolves the device via MCP, and asks the user which of three fixes to apply (exclude device / exclude states / drop + rebuild table). Reverts every patch on the way out; grep-verifies cleanup. Interactive only. One device per pass. SQL DROP is hardcoded to the specific device ID extracted during the run — never parameterised. Bumps plugin version 1.7.0 → 1.8.0 (minor: new user-visible feature). Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- commands/debug-sqllogger.md | 72 ++++++++ skills/debug-sqllogger/SKILL.md | 303 ++++++++++++++++++++++++++++++++ 4 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 commands/debug-sqllogger.md create mode 100644 skills/debug-sqllogger/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 952eac1..6f8d390 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ "name": "indigo", "source": "./", "description": "Indigo home automation development toolkit \u2014 plugin development, API integration, and control page building", - "version": "1.7.0", + "version": "1.8.0", "repository": "https://github.com/simons-plugins/indigo-claude-plugin", "license": "MIT", "keywords": [ diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7f4fa9a..ad2b3c5 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "indigo", - "version": "1.7.0", + "version": "1.8.0", "description": "Indigo home automation development toolkit \u2014 plugin development, API integration, and control page building", "repository": "https://github.com/simons-plugins/indigo-claude-plugin" } diff --git a/commands/debug-sqllogger.md b/commands/debug-sqllogger.md new file mode 100644 index 0000000..084e18b --- /dev/null +++ b/commands/debug-sqllogger.md @@ -0,0 +1,72 @@ +--- +name: debug-sqllogger +description: Debug + fix SQL Logger device history errors — surfaces the hidden per-device exception, identifies the failing device, and walks through the three fix options +--- + +# SQL Logger Debug & Fix + +**Plugin**: https://github.com/simons-plugins/indigo-claude-plugin +**Slash command**: `/indigo:debug-sqllogger` + +## Description + +Indigo's SQL Logger plugin swallows per-device exceptions behind +`logger.debug(..., exc_info=True)`. With `sqlDebugLogging` disabled +(default), the event log only shows a generic: + +``` +SQL Logger Error: One or more failures updating device history; see the debug log for details +``` + +repeating every ~60s — no device ID, no traceback. + +This command temporarily patches SQL Logger's two swallowed +`logger.debug` call sites to `error` level (tagged `[DEBUG-PATCH]`), +surfaces the real exception, names the failing device via MCP, and +then asks which of three fixes to apply: + +- (a) exclude the device entirely from SQL logging +- (b) exclude specific states only +- (c) drop + rebuild the `device_history_` table + +Every patch is reverted on the way out. Grep confirms cleanup. + +**Interactive only.** Each phase waits for explicit user input. + +## On Command Load + +1. Read `skills/debug-sqllogger/SKILL.md` — complete workflow +2. Begin at Phase 1 (CONFIRM + DISCOVER) + +## Workflow Summary + +See `skills/debug-sqllogger/SKILL.md` for the authoritative sequence. +At-a-glance phases: + +1. **CONFIRM + DISCOVER** — verify errors are actively repeating + (`query_event_log`), locate SQL Logger plugin + log via + `mcp__indigo__list_plugins` +2. **PATCH** — promote the two `logger.debug(..., exc_info=True)` + calls in `_update_device_history` and `_create_table_for_dev` to + `logger.error` tagged `[DEBUG-PATCH]`, restart plugin +3. **EXTRACT** — read plugin log, pull `device_history_` and + exception class, resolve device via `get_device_by_id` +4. **REPORT + ASK** — present findings + three fix options, wait for + user choice +5. **APPLY** — execute chosen fix only +6. **REVERT** — restore `logger.debug` calls, remove any + `startup()` patch, confirm `grep [DEBUG-PATCH]` returns zero +7. **VERIFY** — watch event log for ~2 minutes; report outcome + +## Safety + +All safety rules live in `skills/debug-sqllogger/SKILL.md`. +Highlights: every edit tagged `[DEBUG-PATCH]`, grep-verified revert, +never hardcode plugin paths, never DROP a table not just identified, +interactive only, one device per pass. + +## Related Commands + +- `/indigo:dev` — SDK reference if SQL Logger internals have drifted +- `/indigo:update-plugins` — source of the mount-prefix / `PlistBuddy` + patterns reused here diff --git a/skills/debug-sqllogger/SKILL.md b/skills/debug-sqllogger/SKILL.md new file mode 100644 index 0000000..2807de6 --- /dev/null +++ b/skills/debug-sqllogger/SKILL.md @@ -0,0 +1,303 @@ +--- +name: debug-sqllogger +description: >- + This skill should be used when the user asks to "debug SQL Logger", + "find which devices are causing SQL Logger errors", "fix SQL Logger + errors", "SQL Logger integer out of range", "device history errors", + "SQL Logger failures updating device history", or is looking at + repeated "SQL Logger Error: One or more failures updating device + history" messages in the Indigo event log. Surfaces the hidden + per-device exception, identifies the failing device and column, + presents three fix options, and reverts all temporary patches on + the way out. +--- + +# SQL Logger Debug & Fix + +Indigo's SQL Logger plugin swallows per-device exceptions behind +`self.logger.debug(..., exc_info=True)`. With `sqlDebugLogging` +disabled (the default), the only surface is a generic message +repeating every ~60s: + +``` +SQL Logger Error: One or more failures updating device history; see the debug log for details +``` + +No device ID, no traceback, nothing actionable. This skill uses a +temporary `[DEBUG-PATCH]` edit to promote those swallowed debug calls +to `error` level, extracts the failing `device_history_` and root +exception, names the culprit device via MCP, asks the user which of +three fixes to apply, and then reverts every patch on the way out. + +**Interactive only.** Every patch, DB operation, and revert is +confirmed with the user. Never leave `[DEBUG-PATCH]` in the file. + +## Prerequisites + +- `mcp__indigo__*` MCP tools available and connected to the Indigo + server +- Write access to the Indigo `Plugins/` directory — usually via a + mounted network volume if this skill runs on a different Mac than + the Indigo server (see `/indigo:update-plugins` references for the + mount-prefix pattern) + +## Workflow + +### Phase 1 — CONFIRM + DISCOVER + +Confirm the error is actively repeating: + +``` +mcp__indigo__query_event_log(filter="SQL Logger Error", limit=20) +``` + +If there are no recent hits, stop — nothing to debug. If there are +hits, note the cadence (typically ~60s). + +Locate the SQL Logger plugin: + +``` +mcp__indigo__list_plugins → entry where name == "SQL Logger" +``` + +Capture `id` (bundle ID, commonly +`com.perceptiveautomation.indigoplugin.sql-logger`) and `path`. Apply +a mount prefix if the reported path isn't directly accessible (same +pattern `/indigo:update-plugins` uses). + +Derive the two working paths: + +- **Source:** `/Contents/Server Plugin/plugin.py` +- **Plugin log:** `/Logs/indigoplugin.sql-logger/plugin.log` + (same `` as ``, just replace + `Plugins/SQL Logger.indigoPlugin` with `Logs/indigoplugin.sql-logger`) + +### Phase 2 — PATCH + +Locate the two call sites in `plugin.py` with `Grep`: + +- `_update_device_history` — look for `logger.debug` near + `"Failed to update table"` (historically ~line 529) +- `_create_table_for_dev` — look for `logger.debug` near + `"Failed to create table"` (historically ~line 476) + +Line numbers drift across SQL Logger versions — always locate by +content, not by number. + +Promote each to `logger.error` and prefix the message with +`[DEBUG-PATCH] `. Use `Edit`, not `Write` — do not rewrite the file. + +Before: + +```python +self.logger.debug( + f"Failed to update table {dev_table_name} for device {dev.id}: {err}", + exc_info=True, +) +``` + +After: + +```python +self.logger.error( + f"[DEBUG-PATCH] Failed to update table {dev_table_name} for device {dev.id}: {err}", + exc_info=True, +) +``` + +Every patched line MUST contain the literal string `[DEBUG-PATCH]` — +the revert step relies on grep returning zero hits. + +Restart the plugin: + +``` +mcp__indigo__restart_plugin(plugin_id=) +``` + +### Phase 3 — EXTRACT + +Ask the user to wait one error cycle (~60s) and signal when ready. +Do not sleep blindly — the cadence varies with server load. + +Read the last 200 lines of the plugin log and search for +`[DEBUG-PATCH]`. The first matching line names the failing table: + +``` +[DEBUG-PATCH] Failed to update table device_history_1234567 for device 1234567: integer out of range +``` + +Extract: + +- `deviceId` from `device_history_` +- Exception class + message from the traceback below (commonly + `indigologger.postgresql.exceptions.NumericRangeError: integer out of range` + with `CODE: 22003` — classic INT4 overflow) +- If `NumericRangeError`: the `insert_row` traceback names the row + data; cross-reference with the device state list to guess the + offending column + +Look up the device: + +``` +mcp__indigo__get_device_by_id(device_id=) +``` + +Capture name, type, and state list. Columns that hold large +cumulative counters (byte totals, uptime seconds, packet counts on +UniFi-style devices) are the usual INT4 overflow culprits. + +### Phase 4 — REPORT + ASK + +Present the findings and the three fix options. Do not pre-select. + +``` +Device: (id: , type: ) +Failing table: device_history_ +Exception: : +Likely cause: + +Three fix options: + + (a) EXCLUDE DEVICE entirely from SQL logging + - sharedProps["sqlLoggerIgnoreStates"] = "*" + - No further history recorded for this device + - Fully reversible + + (b) EXCLUDE SPECIFIC STATES only + - sharedProps["sqlLoggerIgnoreStates"] = "state1,state2" + - History kept for other states on the device + - Fully reversible + + (c) DROP + REBUILD the table + - DROP TABLE device_history_ + - SQL Logger recreates it on next insert with column types + inferred from *current* values (BIGINT where INT4 overflowed) + - Past history for this device is lost; future history preserved + - Requires a second patch cycle (one-shot DROP in startup()) + +Which fix? (a / b / c / none) +``` + +If the user answers (b), ask which states before proceeding. If +"none", jump to Phase 6 (revert only, no fix applied). + +### Phase 5 — APPLY (chosen option only) + +**Option (a) — exclude device:** + +Setting `sharedProps` cannot be done cleanly through the current MCP +surface. Prefer the Indigo UI path: + +> Indigo → Device → Edit → Edit Device Settings → SQL Logger → +> enable "Ignore all states" + +Tell the user exactly that and wait for them to confirm. Do not try +to write `sharedProps` via another patched function — higher risk +than the problem warrants. + +**Option (b) — exclude specific states:** + +Same UI path as (a), but they enter a comma-separated list of state +names. Pass them the list derived from the traceback + state list in +Phase 3. + +**Option (c) — drop + rebuild:** + +Add a one-shot DROP block at the very end of `startup()` in +`plugin.py` — after `_connect_db()`. Substitute the real device ID: + +```python +# [DEBUG-PATCH] one-shot drop of broken device_history_ +try: + self.indigo_db.execute_non_query( + "DROP TABLE IF EXISTS device_history_" + ) + self.logger.error( + "[DEBUG-PATCH] dropped device_history_ — will be recreated on next insert" + ) +except Exception as err: + self.logger.error(f"[DEBUG-PATCH] drop failed: {err}", exc_info=True) +``` + +Restart the plugin and watch the log for the +`[DEBUG-PATCH] dropped` confirmation line. If the drop fails, stop +and surface the error — do not retry silently. + +**Never run DROP against any table other than the exact +`device_history_` extracted in Phase 3.** Hardcode the specific +ID into the SQL string; never accept it from anywhere except this +skill's own extraction output. + +### Phase 6 — REVERT + +Undo every patch. Up to three regions may need reverting: + +1. `_update_device_history` logger call — restore to + `self.logger.debug(...)`, remove `[DEBUG-PATCH]` prefix +2. `_create_table_for_dev` logger call — same +3. `startup()` one-shot DROP block (option c only) — delete the whole + try/except block + +Verify cleanup with Grep: + +``` +Grep pattern="DEBUG-PATCH" path="" output_mode="count" +``` + +Must return 0. If any hit remains, fix it before continuing — a +leftover DROP in `startup()` will re-drop the rebuilt table on every +plugin restart. + +Restart the plugin one final time. + +### Phase 7 — VERIFY + +Watch for recurrence over ~2 minutes: + +``` +mcp__indigo__query_event_log(filter="SQL Logger Error", limit=10) +``` + +Expected outcomes: + +- **Option (a)/(b):** the device is no longer inserted, so the error + stops on the next cycle +- **Option (c):** SQL Logger recreates the table with BIGINT columns + on the next state change; errors stop once one successful insert + lands + +If errors continue after two cycles: + +- Different device? The original "one **or more** failures" is plural + for a reason. Re-run from Phase 2 — the next iteration will surface + a different `device_history_` in the log. +- Fix didn't land? Check the sharedProp in Indigo UI directly, or + grep the plugin log for the post-fix behaviour. +- Drop rebuild failed? Look for `_create_table_for_dev` exceptions in + the log (the patched version of that call site will surface them). + +Report the outcome to the user plainly: what changed, what's still +happening, what to do next. + +## Safety Rules + +- **Every temporary edit is tagged `[DEBUG-PATCH]`.** Phase 6 relies + on `grep [DEBUG-PATCH]` returning zero. +- **Never leave a patch in the file.** A leftover `startup()` DROP + will re-destroy the rebuilt table on every restart. +- **Never hardcode `Plugins/...` paths.** Discover via + `mcp__indigo__list_plugins` and apply the workspace mount prefix. +- **Never DROP a table the skill hasn't just identified.** The SQL + string must embed the specific `` extracted in Phase 3. +- **Interactive only.** Every phase waits for the user — no + background runs, no cron. +- **One device per pass.** If multiple devices are failing, finish + one end-to-end (Phase 2 → 7) before patching again for the next. + +## Related Skills + +- **`/indigo:dev`** — Indigo SDK reference. Useful if SQL Logger's + internal function names or signatures have drifted from what this + skill describes. +- **`/indigo:update-plugins`** — source of the mount-prefix and + `PlistBuddy` patterns this skill reuses. From b54d2cbfa06fb9186f9fed9187d67998a73eceff Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Sat, 18 Apr 2026 15:40:12 +0100 Subject: [PATCH 2/2] address PR review feedback on debug-sqllogger skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five fixes from code-reviewer + direct source verification against the installed SQL Logger plugin on jarvis: 1. Correct the create-path patch example — line 476 in the real SQL Logger source does NOT carry exc_info=True (only line 529 does). Skill now shows both before/after snippets accurately and tells agents to add exc_info=True when promoting, otherwise the traceback never lands in the log and the patch is useless. 2. Fix the update-path example message text to match reality ("with device changes" not "for device {dev.id}"). Edit-tool matching would have failed cold otherwise. 3. Inline the mount-prefix probe (same loop used in update-plugins/references/install-workflow.md § "Deploy path portability") so a cold agent doesn't have to go hunting, and add an explicit canonical reference for the full pattern. 4. Promote the "revert on any abort" rule to a Safety Rule and wire it into Phase 4's "none" branch. Previously nothing prevented a mid-flow interrupt from leaving [DEBUG-PATCH] logger.error calls in plugin.py spamming the event log every 60s. 5. Clarify Phase 7's "re-run from Phase 2" — Phase 6 has already reverted the patch, so the "next device" case needs a full Phase 2 → 7 re-patch cycle, not just re-reading the log. Also: defence-in-depth on the Option (c) DROP statement — assert matches ^[0-9]+$ before embedding in SQL. Indigo device IDs are always ints, but a Phase 3 extraction bug could theoretically produce something else; cheap to guard. Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/debug-sqllogger/SKILL.md | 116 ++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 35 deletions(-) diff --git a/skills/debug-sqllogger/SKILL.md b/skills/debug-sqllogger/SKILL.md index 2807de6..e62824c 100644 --- a/skills/debug-sqllogger/SKILL.md +++ b/skills/debug-sqllogger/SKILL.md @@ -60,10 +60,27 @@ Locate the SQL Logger plugin: mcp__indigo__list_plugins → entry where name == "SQL Logger" ``` -Capture `id` (bundle ID, commonly -`com.perceptiveautomation.indigoplugin.sql-logger`) and `path`. Apply -a mount prefix if the reported path isn't directly accessible (same -pattern `/indigo:update-plugins` uses). +Capture `id` (bundle ID: `com.perceptiveautomation.indigoplugin.sql-logger`) +and `path`. If the reported path isn't directly accessible (Indigo +runs on a different Mac than this skill — common in this workspace), +apply the workspace mount-prefix probe: + +```bash +MCP_REPORTED_PATH="..." # from mcp__indigo__list_plugins +DEPLOY_PATH="$MCP_REPORTED_PATH" +if [ ! -d "$DEPLOY_PATH/Contents" ]; then + for prefix in "/Volumes/Macintosh HD-1" "/Volumes/Macintosh HD"; do + if [ -d "${prefix}${MCP_REPORTED_PATH}/Contents" ]; then + DEPLOY_PATH="${prefix}${MCP_REPORTED_PATH}" + break + fi + done +fi +``` + +If neither prefix resolves, stop and ask the user for the mount +prefix. See `skills/update-plugins/references/install-workflow.md` +§ "Deploy path portability" for the canonical version of this logic. Derive the two working paths: @@ -74,39 +91,49 @@ Derive the two working paths: ### Phase 2 — PATCH -Locate the two call sites in `plugin.py` with `Grep`: - -- `_update_device_history` — look for `logger.debug` near - `"Failed to update table"` (historically ~line 529) -- `_create_table_for_dev` — look for `logger.debug` near - `"Failed to create table"` (historically ~line 476) +Locate the two call sites in `plugin.py` with `Grep`. Line numbers +drift across SQL Logger versions — always locate by the message +fragment, not by number: -Line numbers drift across SQL Logger versions — always locate by -content, not by number. +- Update path — grep `Failed to update table`. At time of writing + (bundle `com.perceptiveautomation.indigoplugin.sql-logger` 2025.x) + it's at ~line 529 and already carries `exc_info=True`. +- Create path — grep `Failed to create table .* for device history`. + Currently at ~line 476 and does **not** carry `exc_info=True`. -Promote each to `logger.error` and prefix the message with -`[DEBUG-PATCH] `. Use `Edit`, not `Write` — do not rewrite the file. +Promote each to `logger.error`, prefix the message with +`[DEBUG-PATCH] `, and ensure `exc_info=True` is present on both (add +it to the create call if missing — without it the traceback never +reaches the log, which defeats the point of the patch). Use `Edit`, +not `Write`. -Before: +Update path before/after: ```python -self.logger.debug( - f"Failed to update table {dev_table_name} for device {dev.id}: {err}", - exc_info=True, -) +# before (~line 529, with exc_info=True already) +self.logger.debug(f"Failed to update table {dev_table_name} with device changes: {err}", exc_info=True) + +# after +self.logger.error(f"[DEBUG-PATCH] Failed to update table {dev_table_name} with device changes: {err}", exc_info=True) ``` -After: +Create path before/after (note: source has no `exc_info=True` — add +it when promoting): ```python -self.logger.error( - f"[DEBUG-PATCH] Failed to update table {dev_table_name} for device {dev.id}: {err}", - exc_info=True, -) +# before (~line 476, no exc_info) +self.logger.debug(f"Failed to create table {table_name} for device history: {err}") + +# after +self.logger.error(f"[DEBUG-PATCH] Failed to create table {table_name} for device history: {err}", exc_info=True) ``` -Every patched line MUST contain the literal string `[DEBUG-PATCH]` — -the revert step relies on grep returning zero hits. +If a `grep` finds the fragment but the surrounding arguments differ +from the above (SQL Logger is maintained; call signatures drift), +adapt — the invariant is *promote to error, add the DEBUG-PATCH tag, +ensure exc_info=True*. Every patched line MUST contain the literal +string `[DEBUG-PATCH]` — the revert step relies on grep returning +zero hits. Restart the plugin: @@ -179,7 +206,9 @@ Which fix? (a / b / c / none) ``` If the user answers (b), ask which states before proceeding. If -"none", jump to Phase 6 (revert only, no fix applied). +"none" — or at any abort path — jump directly to Phase 6 (revert +only, no fix applied). See Safety Rules: patches must never outlive +the skill's own exit, regardless of cause. ### Phase 5 — APPLY (chosen option only) @@ -203,8 +232,14 @@ Phase 3. **Option (c) — drop + rebuild:** +Before emitting any SQL, assert `` is purely decimal digits +(`^[0-9]+$`). Indigo device IDs are always integers, so a non-match +means the extraction in Phase 3 went wrong — stop and re-run +extraction rather than continuing with a malformed DROP. + Add a one-shot DROP block at the very end of `startup()` in -`plugin.py` — after `_connect_db()`. Substitute the real device ID: +`plugin.py` — after `_connect_db()`. Substitute the validated device +ID: ```python # [DEBUG-PATCH] one-shot drop of broken device_history_ @@ -269,12 +304,16 @@ Expected outcomes: If errors continue after two cycles: - Different device? The original "one **or more** failures" is plural - for a reason. Re-run from Phase 2 — the next iteration will surface - a different `device_history_` in the log. + for a reason. Re-run the **full Phase 2 → 7 cycle** — Phase 6 has + already reverted the previous patch, so the log no longer carries + `[DEBUG-PATCH]` lines. Re-patching is required to surface the next + device. - Fix didn't land? Check the sharedProp in Indigo UI directly, or grep the plugin log for the post-fix behaviour. -- Drop rebuild failed? Look for `_create_table_for_dev` exceptions in - the log (the patched version of that call site will surface them). +- Drop rebuild failed? Re-apply the Phase 2 patch and look for + `_create_table_for_dev` exceptions in the log — with `exc_info=True` + now present on both sites, the traceback will show why the rebuild + insert failed. Report the outcome to the user plainly: what changed, what's still happening, what to do next. @@ -283,12 +322,19 @@ happening, what to do next. - **Every temporary edit is tagged `[DEBUG-PATCH]`.** Phase 6 relies on `grep [DEBUG-PATCH]` returning zero. -- **Never leave a patch in the file.** A leftover `startup()` DROP - will re-destroy the rebuilt table on every restart. +- **Patches never outlive the skill's exit.** If the skill aborts at + any point after Phase 2 — user cancels, log-read finds nothing, + extraction fails, any error, interrupt, or user "none" in Phase 4 — + the first action before exiting is a full Phase 6 revert + (restore both `logger.debug` call sites, remove any `startup()` + DROP block, grep-verify zero `[DEBUG-PATCH]` hits, restart plugin). + A patched `logger.error` left behind will spam the event log every + ~60s at error level until noticed. - **Never hardcode `Plugins/...` paths.** Discover via `mcp__indigo__list_plugins` and apply the workspace mount prefix. - **Never DROP a table the skill hasn't just identified.** The SQL - string must embed the specific `` extracted in Phase 3. + string must embed the specific `` extracted in Phase 3, and + `` must be confirmed decimal-only (`^[0-9]+$`) before emission. - **Interactive only.** Every phase waits for the user — no background runs, no cron. - **One device per pass.** If multiple devices are failing, finish