Skip to content

fix: say when a date filter or task date names a day the calendar lacks - #668

Merged
aliasunder merged 5 commits into
mainfrom
fix/filter-date-calendar-message
Oct 11, 2026
Merged

aliasunder merged 5 commits into
mainfrom
fix/filter-date-calendar-message

Conversation

@aliasunder

@aliasunder aliasunder commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

A date in YYYY-MM-DD form that names a day the calendar lacks, such as 2026-02-30, was answered with "Use YYYY-MM-DD", the form the caller had already used. A model told only the format sends the same date again. These inputs now say the date is not a calendar date, and text that is not in YYYY-MM-DD form keeps the format message:

Input Form wrong (unchanged) Day missing (new)
vault_search created / modified bounds invalid created.on date: "March 10". Use YYYY-MM-DD (e.g. 2026-07-03). created.on date "2026-02-30" is not a calendar date. Pass a real date in YYYY-MM-DD format.
vault_list_tasks date filters (all six) invalid due.before date: "July 3rd". Use YYYY-MM-DD (e.g. 2026-07-03). done.after date "2026-04-31" is not a calendar date. Pass a real date in YYYY-MM-DD format.
vault_create_task / vault_update_task due, scheduled, start, created invalid date: due "2026-2-3" (use YYYY-MM-DD) invalid date: due "2026-02-30" is not a calendar date. Pass a real date in YYYY-MM-DD format.

The task-date messages keep their invalid date prefix, so the two task tools' Errors entry ("a date param fails calendar validation; pass a real YYYY-MM-DD date") still describes both cases and their text is unchanged. Covering the task dates as well as the filters makes every date input that had this message give the same answer.

Changes

  • src/utils/classify-iso-date.ts (new): classifyIsoDate sorts text into a real YYYY-MM-DD day, text not in that form, or a month or day that doesn't exist. Luxon checks the form first (unparsable) and then the calendar (unit out of range), which is the split.
  • search-queries.ts: assertFilterDate uses it for the note and task date filters, under its own section banner, with a note that vault_search and vault_list_tasks quote both messages.
  • task-mutations.ts: validateDate is renamed assertTaskDate (matching assertFilterDate) and uses it for task create and update.
  • Tool text: the Errors entry in vault_search (both descriptions) and vault_list_tasks splits into one bullet per remedy. vault_search's Filters and Errors sections, which were word-for-word copies in its two descriptions, are now one shared string each; the text clients receive is byte-identical to the split above (snapshots unchanged by that commit).
  • Tool surface +292 characters on both size-capped combinations: default 136,588 → 136,880 and embedding-off 135,372 → 135,664, against a cap of 139,029.

Tests

  • classify-iso-date.test.ts: real days, missing days (February 30, February 29 outside a leap year, month 13, month 00, day 00), and malformed text (single-digit parts, a timestamp, a five-digit year, slashes, a month name, whitespace, empty).
  • Both message tiers on the search filters (now including created.after), the task filters, and task create and update for every date field, each asserting the full message.
  • server-error-contracts.test.ts: both tiers for vault_search and vault_list_tasks over HTTP; the two task-date contracts now assert the full result.
  • A separate test-only commit makes 28 more toThrow assertions in task-mutations.test.ts and hybrid-search.test.ts exact: they passed a string, which matches any message containing it, and now pass an Error, which vitest compares whole. Six of them had named only part of the message.
  • Mutation checks: making the classifier never report a missing day fails 13 tests on the expected message; eight more mutations in the review's test audit each fail the intended test.
  • Full suite on macOS, Node 24: 117 files, 5,313 passed, 2 skipped. Typecheck, lint, knip and the tool-surface snapshot pass.

Review

  • Ship-check phases 1–5: no correctness or security findings; the code-quality pass clarified comments and made the rename and the shared vault_search sections above; the test audit added the coverage listed above.
  • Tool-definition review (main's tool list against this branch's, default and embedding-off): only vault_search and vault_list_tasks changed, every fact of the old bullet survives, both new failures are listed, and the unchanged task-tool bullet is still accurate. It also listed older gaps in parts of these two tools this PR does not touch:
    • Deferred to the next tool-description round, which re-grades these tools once: vault_search's opening omits file content, filters.type is case-sensitive but unsaid, filters.related's value format, two repeated sentences, Returns' type/tags empty values, and vault_list_tasks' opening repeating its Returns fields. The code-quality pass's two wording proposals (an invalid <bound> date placeholder, and splitting the task tools' date bullet) went to the same round.
    • Dismissed: database and corrupt-index errors from the search and task queries have no Errors entry. They are rare server faults with no remedy a caller can act on.

Live Test Results

Head: 0dfbd88

Image: ghcr.io/aliasunder/vault-cortex:test, built from this head by Test Deploy run 38020919921 (succeeded 03:37 UTC) and serving the instance. The "before" column was taken over the same connector just before the deploy; every "after" message is new code, which also confirms the container was re-created from the test build.

Probe Before After
vault_search filters.modified.on: "2026-02-30" invalid modified.on date: "2026-02-30". Use YYYY-MM-DD (e.g. 2026-07-03). modified.on date "2026-02-30" is not a calendar date. Pass a real date in YYYY-MM-DD format.
vault_search filters.created.before: "March 10" — invalid created.before date: "March 10". Use YYYY-MM-DD (e.g. 2026-07-03). (unchanged form message)
vault_list_tasks due.on: "2026-02-30" invalid due.on date: "2026-02-30". Use YYYY-MM-DD (e.g. 2026-07-03). due.on date "2026-02-30" is not a calendar date. Pass a real date in YYYY-MM-DD format.
vault_list_tasks done.after: "2026-7-3" — invalid done.after date: "2026-7-3". Use YYYY-MM-DD (e.g. 2026-07-03). (a single-digit month and day is a form error)
vault_create_task due: "2026-02-30" invalid date: due "2026-02-30" (use YYYY-MM-DD) invalid date: due "2026-02-30" is not a calendar date. Pass a real date in YYYY-MM-DD format.
vault_update_task scheduled: "2026-13-01" — invalid date: scheduled "2026-13-01" is not a calendar date. Pass a real date in YYYY-MM-DD format.
vault_update_task start: "tomorrow" — invalid date: start "tomorrow" (use YYYY-MM-DD) (unchanged form message)
vault_search filters.created.on: "2026-10-09", folder of session logs — 3 results, all created 2026-10-09
vault_list_tasks done.on: "2026-10-09" on this repo's board — total 6, all done 2026-10-09 (the first call hit a transient Cloudflare 502 right after the restart; the retry a minute later succeeded)

The task probes named a note path that does not exist; the date check runs first, so nothing was written (the folder lists empty afterwards).

Rebased onto main at 8a512669 as fd3c11004f1fde4db9095413bce095af7a75a196 with no conflicts. The diff outside test files is unchanged (git patch-id --stable over it is cae8ea9f before and after), so the live test above still covers this head. The full suite passes on the rebased head: 117 files, 5,346 tests.

🤖 Generated with Claude Code

Comment thread src/utils/classify-iso-date.ts
@umm-actually

umm-actually Bot commented Oct 10, 2026

Copy link
Copy Markdown

Validate modifiedOnDate's date before converting it
Low severity · subtle bugs · low confidence

src/vault-mcp/search/search-queries.ts:1580 — beyond the diff's line ranges, in code the changes touch or depend on.

Pre-existing: modifiedOnDate hands its caller-supplied date straight to dayToEpochMsRange, so a date the calendar lacks is answered with the converter's own outcome (its internal failure, or bounds that match nothing) instead of the calendar-date message this PR now raises for every other date input. The guard the PR added to the sibling modified filter is the fix template.

Failure scenario: A caller passes a date the calendar lacks to modifiedOnDate (e.g. { date: "2026-02-30" } via the daily-review prompt's date argument): dayToEpochMsRange runs on an impossible day with no calendar check, so the caller gets the converter's internal error or an empty day listing — a model asking about that day is never told the date does not exist.

Suggested fix
Reject params.date with classifyIsoDate before dayToEpochMsRange — or route it through assertFilterDate with a label of its own — so a malformed or impossible date fails the same two ways the filter bounds now do.

umm-actually · deepseek/deepseek-v4.1-flash

@umm-actually

umm-actually Bot commented Oct 10, 2026

Copy link
Copy Markdown

Validate modifiedOnDate's date before converting it
Low severity · correctness · low confidence

src/vault-mcp/search/search-queries.ts:1727 — beyond the diff's line ranges, in code the changes touch or depend on.

modifiedOnDate feeds params.date straight into dayToEpochMsRange, while the modified filter this PR hardens asserts the bound first precisely so the converter never sees a bad date (the new comment states that ordering). A calendar-invalid or malformed date reaching the daily-review prompt's activity lookup therefore fails at the converter instead of returning the shared 'not a calendar date' remediation.

Failure scenario: daily-review invoked with date '2026-02-30' (or 'yesterday') → modifiedOnDate passes it to dayToEpochMsRange unvalidated → the converter's luxon parse fails (or NaN epoch bounds reach the SQL bind) and the prompt degrades with a generic fallback, with no 'is not a calendar date' guidance for the caller.

Suggested fix
Call `assertFilterDate(params.date, "modified")` before `dayToEpochMsRange(params.date)` in modifiedOnDate, so the shared two-tier remediation covers that call site too.

umm-actually · z-ai/glm-5.3-flash

@umm-actually

umm-actually Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

umm-actually re-reviewed at fd3c110

1 new finding(s) posted (4 tracked finding(s) across all runs).

Context notes
  • 18 changed file(s) excluded from review: src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json (diff_exclude_paths input), src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json (diff_exclude_paths input)

umm-actually · z-ai/glm-5.3-flash

@aliasunder

Copy link
Copy Markdown
Owner Author

Replying to the two umm-actually findings "Validate modifiedOnDate's date before converting it" (comments 6093266719 and 6093266800): this is intentional, because neither failure scenario can occur.

  • modifiedOnDate has one caller, the daily-review prompt. Its date argument schema applies ISO_DATE_REGEX, so "yesterday" or any other text not in YYYY-MM-DD form is rejected before the handler runs.
  • For a value in that form that names a missing day, such as 2026-02-30, the handler computes the next day first (DateTime.fromISO(dateArg).plus({ days: 1 }).toISODate()), gets null, and returns at "Could not compute the next day" before getDailyNote or modifiedOnDate runs.
  • dayToEpochMsRange never produces NaN bounds: it throws on anything but a real YYYY-MM-DD day, so a future caller that skipped validation would fail closed rather than bind NaN into the SQL.

The prompt's own wording for a missing day ("Could not compute the next day…") only names the format. That is the same gap this PR fixes for the filters, but it sits in the daily-review prompt, which another open change rewrites to say the date is not a calendar date.


🔍 ship-check · pr-monitor · claude-opus-5-5

aliasunder and others added 5 commits October 10, 2026 01:49
A date in YYYY-MM-DD form that names a missing day, such as 2026-02-30,
was answered with "Use YYYY-MM-DD", the form the caller had already used.
The vault_search created/modified filters, the vault_list_tasks date
filters, and the vault_create_task / vault_update_task date params now
say the date is not a calendar date; text not in that form keeps the
format message. classifyIsoDate in src/utils/ makes the split for all
of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rtTaskDate, share vault_search's Filters and Errors text

- classifyIsoDate's doc uses the verb it does, names both Luxon reasons, and says a month or day can be out of range
- assertFilterDate and assertTaskDate return early on a real day, and their docs say which tool descriptions quote their messages
- assertFilterDate gets its own section banner, since full-text search and task listing both call it
- comments say why a modified bound never reaches dayToEpochMsRange's check, what a task date filter's column name is used for, and what null means for an update's date params
- both vault_search description variants interpolate one Filters and one Errors section; the served text is unchanged

Ship-Check: code-quality · claude-opus-5-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… exactly

- createTask and updateTask: an impossible scheduled or start date is
  rejected with a message that names the field. No test covered these
  checks before, so dropping one let the date reach the note.
- listTasks: the malformed date filter and missing .md extension tests
  compare the whole error message instead of a substring.

Ship-Check: test-audit · claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every other created and modified bound already had a rejection test; a
created.after bound naming a day the calendar lacks now does too.

Ship-Check: test-audit · claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 assertions passed a string to toThrow, which matches any message that
contains it. They now pass an Error, which vitest compares whole. Six of
them named only part of the message and now name all of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aliasunder
aliasunder force-pushed the fix/filter-date-calendar-message branch from 0dfbd88 to fd3c110 Compare October 10, 2026 05:50
Comment thread src/vault-mcp/search/__tests__/search-index.test.ts
@aliasunder
aliasunder merged commit 7e59246 into main Oct 11, 2026
18 checks passed
@aliasunder
aliasunder deleted the fix/filter-date-calendar-message branch October 11, 2026 15:43
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