Repository navigation
feat(witan): filter task_search by project and assignee - #494
Merged
Merged
Conversation
One teammate's insights report shows 18-20 malformed task_search / task_list calls per session. `task_search` took only query, repo and status, while `witan tasks QUERY` also takes --project and --assignee, so one likely cause (not reproduced) is an agent passing a filter the tool did not have. The CLI offered those filters by narrowing the server's 20 hits client-side, so a project's match ranked below the unfiltered top 20 was never found. `task_search` now takes both. A project search filters in the match and spans every repo; assignee (including "@me") filters in Python over an uncapped candidate read, and the 20-hit cap applies after either filter. The CLI passes its flags through instead of filtering itself. The docstring points "what's next in a project" at task_ready/task_list. Task: tk-task-search-malformed-mcp-calls-loop-18-20-times-66de52 Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
No skill named task_search or a CLI fallback, and the insights tool told people to add `witan task search`, which does not exist. The new "Find a task" section maps "the next task in a project" to task_ready/task_list, keywords to task_search with its real filters, and names `witan tasks "<query>" --project wp-...` as the fallback once the same call has been rejected twice. Task: tk-task-search-malformed-mcp-calls-loop-18-20-times-66de52 Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
The witan UI's search comments said task_search takes no project, which stopped being true with the previous commit; the reason the project view reads the whole project (the 20-hit cap) still holds. The changelog fragment stated the cause of the malformed calls as fact, which was not reproduced. Task: tk-task-search-malformed-mcp-calls-loop-18-20-times-66de52 Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues were identified in the filtering logic or CLI integration, and regression tests cover the principal new behaviors.
0 open findings
What changed in this PR
Adds project and assignee filters to Witan task search, aligning the MCP tool with existing CLI options and avoiding filtering only the top 20 hits.
Changes:
- Adds server-side project and assignee filtering, including
@me. - Passes CLI filters directly to
task_searchand adds regression coverage. - Updates search guidance, generated references, and release notes.
| File | Description |
|---|---|
| mcp/servers/witan/witan/skills/witan-task/SKILL.md | Adds task discovery and CLI fallback guidance. |
| mcp/servers/witan/witan/server.py | Implements filtering before the final result cap. |
| mcp/servers/witan/witan/cli/tasks.py | Forwards project and assignee filters to the server. |
| mcp/servers/witan/ui/src/app.ts | Updates comments explaining project search behavior. |
| mcp/servers/witan/ui/src/app.test.ts | Updates the project search test comment. |
| mcp/servers/witan/tests/test_tasks.py | Tests cross-repo projects, @me, and lower-ranked matches. |
| mcp/servers/witan/tests/test_cli.py | Tests project searches across repositories. |
| mcp/servers/witan/queries/read.gq | Adds broader queries for filtered searches. |
| mcp/servers/witan/docs/CLI_REFERENCE.md | Documents positional keyword search. |
| mcp/servers/witan/changelog.d/20261015_163802_blarghmatey_task_search_filters.md | Records filters and deployment compatibility limits. |
| docs/reference/mcp-tools/tasks.md | Updates generated tool documentation. |
| docs/reference/cli.md | Updates generated search-limit documentation. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
blarghmatey
added a commit
that referenced
this pull request
Oct 16, 2026
…25 helper (#504) #494 called _search_task_bm25(name, params), written against the helper as it was before #500 collapsed the per-filter queries and changed it to take one params dict. The two merged an hour apart, so on main a task_search with project_slug or assignee raises TypeError, and three test_tasks.py tests fail. main's push workflow runs only the ol-agent-kit tests, so nothing caught it after merge. The helper keeps the one-dict signature and takes the base query name as an optional second argument, defaulting to search_tasks, and unions it with its _title twin as before. Claude-Session: https://claude.ai/code/session_015yBunUPNeYQ54pWBaqhCwM
blarghmatey
added a commit
that referenced
this pull request
Oct 16, 2026
`setup --agent codex` and the Codex /hooks trust notice come from witan-core's AgentName and report_install, which change in this PR. Both servers' witan-core floors admitted 0.42 (witan) and 0.41 (witan-code), so upgrading a server while keeping an older core rejected `--agent codex` and dropped the notice from `--agent all`. Both floors move to >=0.43 as behaviour floors, and the three packages release together so the floor exists on PyPI when the servers do. witan-council 0.42.0 also collects every fragment pending since 0.41.0, including #494, #495, #496 and #503. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
blarghmatey
added a commit
that referenced
this pull request
Oct 16, 2026
* fix(witan-code): reindex the files a Codex apply_patch edits Codex's hooks.json accepts the `Edit|Write` matcher witan code setup registers for reindex-hook, and selects its `apply_patch` tool by that alias. The payload names the tool `apply_patch` and carries the patch text as `tool_input.command`, with no file path, so the hook found nothing to reindex once agent-config-kit writes witan's hooks to Codex. reindex-hook now reads the patch's Add File, Update File and Move to headers and reindexes each file the patch leaves on disk. Only those header lines at column 0 count, so a path inside added content is not picked up. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT * feat(witan-core): print install notices and accept --agent codex Since #493, agent-config-kit writes declarative hooks to Codex's hooks.json and returns one notice per new hook, saying Codex won't run it until the user trusts it with /hooks. report_install never printed InstallResult.notices, so `witan setup` and `witan code setup` wrote witan's UserPromptSubmit and Stop hooks to Codex with nothing telling the user they sit inert until trusted. report_install now prints each notice as an action line. `codex` joins AgentName (labelled "Codex CLI"), so both setups can target Codex alone. notices and the codex platform exist only in unreleased agent-config-kit (0.12.0 has neither), so the cli extra's floor moves to >=0.13. That also covers prerequisites, so its getattr fallback goes. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT * fix(witan): close the session from Codex's Stop hook witan session-checkpoint finds the session handle by the agent session id, which it reads from $CLAUDE_SESSION_ID or $PI_SESSION_ID. Codex runs hooks with its own process environment and no session variable, so under Codex the checkpoint would find no handle and leave the session open. The id is only in the hook's stdin payload. The checkpoint now reads stdin and, for a Codex payload (told apart by `turn_id`, which Codex's input schema marks as its extension), passes the payload's session_id as the explicit id. A Claude Code payload is not used, so the Claude path still resolves $CLAUDE_SESSION_ID. The payload id is the one Codex exports to its shell tool as $CODEX_SESSION_ID, so that variable becomes the last fallback in current_session_id, and the workflow skills and workflow_session_start docs tell a Codex agent to pass it. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT * fix(witan-code): accept indented apply_patch file headers Codex's patch parser trims a line before matching an Add File or Update File header outside an update hunk, so an indented header applies but was not reindexed. Move to is only right-trimmed there, so it stays column-0. The reindex-hook docstring now describes the Codex payload as well as Claude Code's. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT * fix(witan): keep session-checkpoint's stdout empty under Codex With a half-configured remote (e.g. WITAN_REMOTE_URL without WITAN_OIDC_ISSUER), _srv() prints its error to stdout before raising SystemExit. The checkpoint swallows the exit, but Codex reads any non-JSON Stop hook stdout as a malformed decision and marks the hook failed. The dispatch now runs with stdout redirected to stderr. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT * fix(witan): drop an inherited CODEX_SESSION_ID from Pi's checkpoint child The Pi extension's checkpointEnv clears CLAUDE_SESSION_ID and PI_SESSION_ID before setting Pi's own id, so a Pi started from another agent can't close that agent's session. This branch made CODEX_SESSION_ID a fallback in current_session_id, and checkpointEnv didn't clear it. A Pi launched from Codex's shell tool whose session manager returns no id would then close the Codex session's handle. The test reads checkpointEnv's source and requires a delete for every variable in SESSION_ID_ENV_VARS, so adding a fourth can't repeat this. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT * doc(witan): name Codex in the session-id fallback docs task_claim's session_id docs, the holder docstring and the provenance reader still listed only CLAUDE_SESSION_ID and PI_SESSION_ID. Codex is in Pi's position: it exports CODEX_SESSION_ID only to its shell tool's commands, so a local stdio witan never sees it and a Codex agent has to pass the id. The session_state comment also said Codex hooks get no session variable at all, when a hook inherits whatever launched Codex. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT * version: witan-core 0.43.0, witan-council 0.42.0, witan-code 0.26.0 `setup --agent codex` and the Codex /hooks trust notice come from witan-core's AgentName and report_install, which change in this PR. Both servers' witan-core floors admitted 0.42 (witan) and 0.41 (witan-code), so upgrading a server while keeping an older core rejected `--agent codex` and dropped the notice from `--agent all`. Both floors move to >=0.43 as behaviour floors, and the three packages release together so the floor exists on PyPI when the servers do. witan-council 0.42.0 also collects every fragment pending since 0.41.0, including #494, #495, #496 and #503. Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One teammate's insights report shows 18-20 malformed
task_search/task_listcalls per session while looking for "the next learner analytics task".task_searchtook onlyquery,repoandstatus, whilewitan tasks "<query>"also takes--projectand--assignee, so one likely cause is an agent reaching for a filter the tool doesn't have. The CLI offered those filters by narrowing the server's 20 hits client-side, which missed a project's match whenever it ranked below the unfiltered top 20.This adds
project_slugandassigneetotask_search(witan tasktk-task-search-malformed-mcp-calls-loop-18-20-times-66de52):search_tasks_by_projectpair inread.gq) and spans every repo, the same waytask_list(project_slug=...)ignoresrepo.assignee(including"@me") filters in Python over a new uncappedsearch_tasks_all_uncappedpair, matched with_holder_matchesas intask_list. Repo, status and the 20-hit cap apply after the filter. The unfiltered path still uses the existing capped queries, sotask_searchwithout filters andtask_create'ssimilarread the same rows as before.witan tasks QUERYpasses--projectand--assigneethrough instead of filtering itself.--readystill intersects withtask_ready.task_ready/task_list(project_slug=...).witan tasks "<query>" --project wp-...(the insights tool suggestedwitan task search, which does not exist), and one rule: if the same call is rejected twice, stop varying it and use the CLI.Two limits I left alone.
witan tasks QUERY --project wp-xwithout--statusstill drops closed rows after the server's 20-row cap, so a project with 20+ closed matches outranking an open one prints fewer rows than exist (this predates the branch, and thequeryhelp text now says so; the UI avoids it with onetask_searchper live status, and the CLI could do the same). An assignee-only search reads every BM25 match in the graph, uncapped and with descriptions, and filters repo/status/assignee in Python; assignee has to be filtered in Python because_holder_matcheswidens an unqualified name to every session, but repo/status could move into uncapped variants of the existing queries if the cost shows up. I did not measure it.A newer CLI talking to a deployment older than this release will get a schema rejection for
witan tasks QUERY --project/--assigneeuntil the deployment is upgraded. A plainwitan tasks QUERYstill works there, because the proxy dropsNonearguments before sending (_map_argsinwitan_core/remote/proxy.py). That is from reading the code, not tried against a live deployment.The original failure is not reproduced. The teammate's transcripts aren't available, so the actual rejected input is still unrecorded. The spec also notes that Claude Code refuses a call whose arguments don't parse as JSON before the server sees it, and resending the same input would loop the same way. The skill rule covers that case too, but this change can't tell which one it was.
Tested with
pytest mcp/servers/witan(whatjust test-witan-councilruns) against omnigraph 0.13.0: 1571 passed, 4 skipped, 1 failed. The failure istest_pre_upgrade_candidates_exclude_the_current_binary, which fails locally because pointing the suite at a non-default omnigraph binary puts two onPATH; it doesn't touch this code. New tests cover a project hit in another repo,assignee="@me", a project/assignee match that ranks past 20 on the unfiltered query, andwitan tasks QUERY --projectacross repos. The existing CLI search tests (closed elision, the 50-row window,--assignee,--ready --status) pass unchanged.prekpasses on the changed files andjust docs-checkis clean.Not done yet: the manual check that asking a Claude Code session for "the next task" converges without a rejected call. That needs a live session, and the transcript excerpt should go on the task.
https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT