Skip to content

fix(work-mcp): harden pillar keyword loading and repair QMD import flag#77

Open
stevegranshaw wants to merge 1 commit into
davekilleen:mainfrom
stevegranshaw:fix/work-mcp-pillar-keyword-and-qmd-flag
Open

fix(work-mcp): harden pillar keyword loading and repair QMD import flag#77
stevegranshaw wants to merge 1 commit into
davekilleen:mainfrom
stevegranshaw:fix/work-mcp-pillar-keyword-and-qmd-flag

Conversation

@stevegranshaw

Copy link
Copy Markdown

Linked Issue

  • N/A — community contribution. Full bug analysis is in this description; happy to open a tracking issue if you'd prefer one.

What Changed

Two root-cause fixes in core/mcp/work_server.py:

  1. Pillar keyword crash. An unquoted YAML token like 1:1 in a user's System/pillars.yaml parses as a base-60 int (61), so guess_pillar()'s keyword in item_lower (work_server.py:313) raises 'in <string>' requires string as left operand, not int. That takes down every pillar-inferring tool — list_tasks, create_task, get_work_summary, get_week_progress. Fix: coerce keywords to str at load (load_pillars_from_yaml), so the loader is robust to any non-string value regardless of how a user's pillars file was written.

  2. create_task NameError. Two HAS_QMD assignments guard different imports:

    • The qmd_query import (is_qmd_available, vault_search) used the wrong path (utils.qmd_query rather than core.utils.qmd_query) and ran before sys.path.insert(..., <vault root>), so it always failed → is_qmd_available never defined.
    • The later qmd_indexer import succeeds (it runs after the sys.path insert) and set HAS_QMD = True, clobbering the earlier False.

    Result: HAS_QMD is True while is_qmd_available is undefined, so find_similar_tasks (:2140) and the meeting-context path (:2613) raise NameError: name 'is_qmd_available' is not defined. (Side note: because the qmd_query import never resolved, QMD semantic de-duplication has effectively been dead code.) Fix: move the qmd_query import to after the sys.path insert with the correct path, and stop the qmd_indexer block from touching HAS_QMD (it has a no-op fallback and one unguarded call site, so it never needed the flag).

Test Plan

  • Verified in a clean Python process (independent of any long-running MCP server):
    • All pillar keywords load as str; guess_pillar returns the right pillar for 1:1, 121, and multi-word titles with no crash.
    • is_qmd_available, vault_search, refresh_search_index all resolve; HAS_QMD == True.
    • With QMD not indexed, is_qmd_available() returns Falsefind_similar_tasks falls back to keyword matching (the existing try/except), so there's no behavior change beyond removing the crash.
  • Negative path: confirmed graceful degradation when QMD isn't installed (import guarded; no-op refresh_search_index intact).
  • Regression test: not included yet. Suggested unit tests: (a) load_pillars_from_yaml returns all-str keywords given an unquoted 1:1; (b) guess_pillar('1:1 sync') does not raise. Glad to add before merge if you'd like.

Risk & Rollback

  • Risk: low. No public API or tool-signature changes. The keyword coercion is a pure boundary normalization; the QMD path stays guarded by is_qmd_available() plus a try/except keyword fallback, so it can't crash task creation. The one behavioral change: on machines where QMD is installed, semantic de-dup now actually runs (its intended, previously-dead behavior).
  • Rollback: revert this commit (four small hunks in one file).

Docs Impact

  • None required. Suggest a CHANGELOG.md entry (e.g. v1.20.2): task tools crashed on certain pillar keywords, and create_task hit a hidden semantic-search error — both fixed.

Found while using Dex; fix written with Claude Code's help and verified before submitting, per CONTRIBUTING.md.

Two root-cause fixes in core/mcp/work_server.py.

1. Coerce pillar keywords to str at load time. An unquoted YAML token
like `1:1` parses as a sexagesimal int (61), which crashes
guess_pillar's `keyword in text` with "'in <string>' requires string as
left operand, not int" -- taking down every pillar-inferring tool
(list_tasks, create_task, get_work_summary, get_week_progress).

2. Repair the QMD semantic-search import. The qmd_query import used the
wrong module path (utils.qmd_query -> core.utils.qmd_query) and ran
before sys.path was set up, so it always failed; a second HAS_QMD
assignment in the qmd_indexer block then set HAS_QMD=True, leaving
is_qmd_available undefined while the flag claimed QMD was available.
create_task and meeting-context lookups then hit
"NameError: name 'is_qmd_available' is not defined". Defer the qmd_query
import to after sys.path setup with the correct path, and stop the
indexer block from touching HAS_QMD.

Verified in a clean Python process: pillars load with all-str keywords,
guess_pillar handles `1:1`/`121` without raising, QMD symbols resolve,
and find_similar_tasks falls back to keyword matching when QMD is not
indexed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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