fix(tasks): remove non-functional reminder write params (v1.1.2)

Exhaustive FW_DEBUG investigation on both Free and Premium accounts
confirmed that taskupdate2 silently ignores all reminder fields regardless
of encoding (flat, JSON-string, PHP-bracket, all variants). Alternative
endpoints (tasksetalert etc.) are unregistered. Root cause: mobile app
Service Worker transforms reminder requests in ways not reproducible via
direct API calls. Reminders remain read-only. Recurrency write confirmed
working (flat encoding). Docs updated with full investigation findings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 21:35:23 +02:00
parent 08ee5fb84a
commit d6d8d40305
5 changed files with 46 additions and 47 deletions
+23
View File
@@ -10,6 +10,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This project follows Semantic Versioning (SemVer). This project follows Semantic Versioning (SemVer).
Breaking changes (removed tools, changed parameters) increment the major version. Breaking changes (removed tools, changed parameters) increment the major version.
## [1.1.2] 2026-04-17
### Fixed
- `update_task`: removed non-functional reminder write parameters (`reminder_unit`,
`reminder_value`, `clear_reminder`) — exhaustive FW_DEBUG testing (flat params,
JSON-string, PHP-bracket, all string values, with/without `localId`, alternative
endpoints) on both Free and Premium accounts confirmed that `taskupdate2` silently
ignores all reminder fields; historical SNOOZE reminders were set via the mobile
app's Service Worker which transforms requests in ways not reproducible via direct API
- `update_task`: docstring now explicitly states reminders are read-only
- SPEC.md: documented reminder write limitation with investigation findings
- CLAUDE.md: updated reminder row to reflect read-only status
### Investigation notes
- Tested formats: flat int params, flat string params, JSON-encoded string value,
PHP-bracket notation (`reminder[reminderUnit]=DAY`), no-`localId` variant
- Tested endpoints: `tasksetalert`, `taskalertput`, `taskreminderset`, `taskalert`,
`tasknotification` — all return `"The call X is not registered"`
- Verified on both test account (Free) and real account (Premium) — same result
- Recurrency write via flat params is confirmed working (separate from reminder)
---
## [1.1.1] 2026-04-17 ## [1.1.1] 2026-04-17
### Fixed ### Fixed
+3 -3
View File
@@ -27,7 +27,7 @@ und wird in Claude Desktop eingebunden.
## Aktueller Stand ## Aktueller Stand
### Version: **v1.1.0** ← aktuell ### Version: **v1.1.2** ← aktuell
### Implementierte Tools ### Implementierte Tools
@@ -133,8 +133,8 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | | | `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | |
| `taskupdate2` | `dueDate` löschen | `$empty` | | `taskupdate2` | `dueDate` löschen | `$empty` |
| `taskupdate2` | `recurrencyDescriptor` (flach!) | `recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence` als Top-Level-Parameter; löschen: `recurrency="NONE"` | | `taskupdate2` | `recurrencyDescriptor` (flach!) | `recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence` als Top-Level-Parameter; löschen: `recurrency="NONE"` |
| `taskupdate2` | `reminder` (flach!) | `reminderUnit: "MINUTE"\|"HOUR"\|"DAY", reminderValue: int, reminderType: "SNOOZE", localId: 0` als Top-Level-Parameter; löschen: `reminderUnit=""` | | `taskupdate2` | **⚠️ Reminder read-only** | `reminderUnit`, `reminderValue`, `reminderType`, `localId` werden von der API ignoriert (verifiziert via FW_DEBUG auf Premium-Account). Service Worker der mobilen App transformiert Reminder-Requests — nicht reproduzierbar via direkter API. |
| `taskupdate2` | **⚠️ Encoding** | FiZ `Ai()`-Encoder sendet alle Felder flach — KEINE verschachtelten Objekte für recurrencyDescriptor/reminder! | | `taskupdate2` | **⚠️ Encoding** | FiZ `Ai()`-Encoder sendet Recurrency-Felder flach (verifiziert). Reminder: read-only. |
| `taskmark` | `taskId`, `complete` | `"true"`/`"false"` | | `taskmark` | `taskId`, `complete` | `"true"`/`"false"` |
| `metadelete` | `id` | metaId des Tasks / Rezepts | | `metadelete` | `id` | metaId des Tasks / Rezepts |
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like | | `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
+11 -7
View File
@@ -208,16 +208,20 @@ POST https://api.familywall.com/api/taskupdate2
| `byMonthDay` | nein | int (z.B. `7` für "am 7. des Monats") | | `byMonthDay` | nein | int (z.B. `7` für "am 7. des Monats") |
| `recurrencyEndDate` | nein | ISO-Datum (z.B. `"2026-12-31"`) | | `recurrencyEndDate` | nein | ISO-Datum (z.B. `"2026-12-31"`) |
| `endOccurence` | nein | int (nach N Wiederholungen aufhören) | | `endOccurence` | nein | int (nach N Wiederholungen aufhören) |
| `reminderUnit` | nein | `"MINUTE"` \| `"HOUR"` \| `"DAY"` (löschen: `""`) |
| `reminderValue` | nein | int (`0` = zum Zeitpunkt, `15` = 15 min vorher); löschen: `0` |
| `reminderType` | nein | immer `"SNOOZE"` |
| `localId` | nein | immer `0` |
**Hinweis:** `taskListId` ist NICHT Pflicht beim Update. **Hinweis:** `taskListId` ist NICHT Pflicht beim Update.
**⚠️ Encoding:** Der FiZ-`Ai()`-Encoder serialisiert alle Felder **flach** als Top-Level-Form-Parameter. **⚠️ Encoding:** Der FiZ-`Ai()`-Encoder serialisiert alle Felder **flach** als Top-Level-Form-Parameter.
`recurrencyDescriptor` und `reminder` sind **keine** verschachtelten JSON-Objekte — die Felder Recurrency-Felder werden direkt auf Top-Level gesendet (verifiziert via xb-Encoder im JS-Bundle).
werden direkt auf Top-Level gesendet (verifiziert via xb-Encoder / fc-Encoder im JS-Bundle).
**⚠️ Reminder-Schreibzugriff nicht möglich:**
Alle Versuche, `reminderUnit`, `reminderValue`, `reminderType`, `localId` via `taskupdate2`
zu setzen, wurden ignoriert — die API antwortet mit HTTP 200 und `lastAction: UPDATED`,
aber `reminder` bleibt unverändert auf dem Default-Wert `{reminderUnit: MINUTE, reminderType: NONE, reminderValue: 0}`.
Auch alternative Encodings (JSON-String, PHP-Bracket, verschachtelt) und alternative Endpoints
(`tasksetalert`, `taskalertput` etc.) wurden erprobt — alle Endpoints sind nicht registriert.
**Ursache:** Reminder-Updates werden vom Service Worker der mobilen App abgefangen und transformiert.
Diese Transformation ist nicht reproduzierbar ohne den Service Worker.
Reminder sind daher **read-only** (wie in v0.9 dokumentiert).
**Response:** **Response:**
``` ```
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" name = "mcp-familywall"
version = "1.1.1" version = "1.1.2"
description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude" description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+8 -36
View File
@@ -930,15 +930,17 @@ def update_task(
recurrency_interval: int | None = None, recurrency_interval: int | None = None,
rrule: str | None = None, rrule: str | None = None,
clear_recurrency: bool = False, clear_recurrency: bool = False,
reminder_unit: str | None = None,
reminder_value: int | None = None,
clear_reminder: bool = False,
) -> str: ) -> str:
"""Update an existing task's fields. """Update an existing task's fields.
IMPORTANT: Ask the user for confirmation before calling this tool. IMPORTANT: Ask the user for confirmation before calling this tool.
At least one parameter besides *task_id* must be provided. At least one parameter besides *task_id* must be provided.
NOTE: Setting or clearing reminders is **not supported** via this tool.
The Family Wall API does not accept reminder writes through ``taskupdate2``
— reminder updates are handled exclusively by the mobile app's Service
Worker and cannot be replicated via direct API calls.
Args: Args:
task_id: Task metaId from get_tasks. task_id: Task metaId from get_tasks.
text: New title text (omit to leave unchanged). text: New title text (omit to leave unchanged).
@@ -967,14 +969,6 @@ def update_task(
Overrides simple recurrency fields when set. Overrides simple recurrency fields when set.
clear_recurrency: Set to ``True`` to remove the recurrence rule from the task. clear_recurrency: Set to ``True`` to remove the recurrence rule from the task.
Cannot be used together with *recurrency*. Cannot be used together with *recurrency*.
reminder_unit: Unit for the reminder offset — ``"MINUTE"``, ``"HOUR"``, or
``"DAY"``. Must be provided together with *reminder_value*. Requires a
*due_date* to be set on the task.
reminder_value: Offset before the due date/time (e.g. ``0`` = at the time,
``15`` = 15 min before, ``1`` with unit ``"DAY"`` = 1 day before).
Must be provided together with *reminder_unit*.
clear_reminder: Set to ``True`` to remove the reminder from the task.
Cannot be used together with *reminder_unit*/*reminder_value*.
Returns: Returns:
JSON success indicator or an error message. JSON success indicator or an error message.
@@ -983,12 +977,6 @@ def update_task(
return _err("'clear_due_date' and 'due_date' cannot be used together.") return _err("'clear_due_date' and 'due_date' cannot be used together.")
if clear_recurrency and recurrency is not None: if clear_recurrency and recurrency is not None:
return _err("'clear_recurrency' and 'recurrency' cannot be used together.") return _err("'clear_recurrency' and 'recurrency' cannot be used together.")
if clear_reminder and (reminder_unit is not None or reminder_value is not None):
return _err(
"'clear_reminder' cannot be used together with 'reminder_unit'/'reminder_value'."
)
if (reminder_unit is None) != (reminder_value is None):
return _err("'reminder_unit' and 'reminder_value' must be provided together.")
if ( if (
text is None text is None
@@ -1000,14 +988,11 @@ def update_task(
and list_id is None and list_id is None
and recurrency is None and recurrency is None
and not clear_recurrency and not clear_recurrency
and reminder_unit is None
and not clear_reminder
): ):
return _err( return _err(
"At least one of 'text', 'description', 'category_id', 'due_date'," "At least one of 'text', 'description', 'category_id', 'due_date',"
" 'clear_due_date', 'assignee_ids', 'list_id', 'recurrency'," " 'clear_due_date', 'assignee_ids', 'list_id', 'recurrency', or"
" 'clear_recurrency', 'reminder_unit'/'reminder_value', or" " 'clear_recurrency' must be provided."
" 'clear_reminder' must be provided."
) )
params: dict[str, Any] = {"metaId": task_id} params: dict[str, Any] = {"metaId": task_id}
@@ -1027,9 +1012,7 @@ def update_task(
if list_id is not None: if list_id is not None:
params["taskListId"] = list_id params["taskListId"] = list_id
# The FiZ Ai() encoder serialises all fields flat at the top level of the # Recurrency fields are sent flat at the top level (verified: FiZ Ai() encoder).
# request body — nested objects are NOT supported. recurrencyDescriptor and
# reminder fields must therefore be sent as plain top-level parameters.
if clear_recurrency: if clear_recurrency:
params["recurrency"] = "NONE" params["recurrency"] = "NONE"
elif recurrency is not None: elif recurrency is not None:
@@ -1039,17 +1022,6 @@ def update_task(
if rrule is not None: if rrule is not None:
params["rrule"] = rrule params["rrule"] = rrule
if clear_reminder:
params["reminderUnit"] = ""
params["reminderValue"] = 0
params["reminderType"] = "SNOOZE"
params["localId"] = 0
elif reminder_unit is not None and reminder_value is not None:
params["reminderUnit"] = reminder_unit
params["reminderValue"] = reminder_value
params["reminderType"] = "SNOOZE"
params["localId"] = 0
try: try:
_authenticated_call("taskupdate2", params) _authenticated_call("taskupdate2", params)
except RuntimeError as exc: except RuntimeError as exc: