Skip to content

fix(mcp): guard against None optional params in measure_drift handler - #325

Merged
Q00 merged 1 commit into
Q00:mainfrom
lifrary:fix/275-measure-drift-none-guard
Apr 6, 2026
Merged

Q00 merged 1 commit into
Q00:mainfrom
lifrary:fix/275-measure-drift-none-guard

Conversation

@lifrary

@lifrary lifrary commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #275 — ouroboros_measure_drift crashes with TypeError: object of type 'NoneType' has no len() when optional array parameters are null.

Root Cause

MeasureDriftHandler.handle() uses arguments.get("constraint_violations", []) to read optional parameters. When an MCP client sends an explicit JSON null for these fields (rather than omitting the key), .get() returns None because the key exists — the default [] is only used when the key is missing.

This None then hits len(constraint_violations_raw) in the log.info() call (line 144), causing the TypeError before the drift engine is even reached.

Fix

Replace .get(key, []) with .get(key) or [] for both optional array parameters. The or [] pattern handles both cases:

  • Key missing → None or [] → []
  • Key present with null → None or [] → []

Changes

  • src/ouroboros/mcp/tools/evaluation_handlers.py: 2-line fix for constraint_violations and current_concepts parameter handling
  • tests/unit/mcp/tools/test_definitions.py: Add regression test that passes None for both optional params and verifies successful drift measurement

Note on issue comments

The issue comments suggest the crash is in drift.py:155,213 (Jaccard similarity calculation), but those lines compute set operations (&, |) which always produce sets, never None. The actual crash occurs earlier in the handler's log.info() call. The drift calculation functions already have if not x: guards that safely handle None.

Test plan

  • TestMeasureDriftHandler::test_handle_none_optional_params — new test, passes
  • All 8 MeasureDriftHandler tests pass
  • All 27 drift engine tests pass (no regression)
  • Full unit suite: 4117 passed, 0 failed
  • ruff check clean

🤖 Generated with Claude Code

…Q00#275)

When MCP clients send `null` for optional array parameters
(`constraint_violations`, `current_concepts`), `dict.get(key, [])`
returns `None` because the key exists — it just has a null value.
This causes `TypeError: object of type 'NoneType' has no len()` at
the `log.info()` call before the drift engine is even reached.

Replace `.get(key, [])` with `.get(key) or []` to handle both
missing keys and explicit null values.

Closes Q00#275

Co-Authored-By: Claude Opus 4.6 <[email protected]>

@ouroboros-agent ouroboros-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: APPROVE

Reviewing commit 50d5bac for PR #325

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

# File:Line Category Finding
1 src/ouroboros/mcp/tools/evaluation_handlers.py:137 API contract arguments.get(... ) or [] fixes the None case, but it also silently normalizes other falsy non-array values like "" or 0 to []. If you want to preserve the declared ARRAY contract more strictly, normalize only None here and let other invalid types fail fast or be validated explicitly.
2 tests/unit/mcp/tools/test_definitions.py:1205 Test gap The new regression test covers constraint_violations=None, but it does not assert the current_concepts=None outcome. Adding an assertion around ontology_drift would lock in the intended behavior for both optional fields.

Design Notes

This is a narrowly scoped fix with an appropriate regression test. The handler remains tolerant of omitted optional inputs, and the change addresses the concrete runtime failure path without affecting the main drift calculation flow.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

@Q00
Q00 merged commit cfad863 into Q00:main Apr 6, 2026
5 checks passed
Sathvik-1007 pushed a commit to Sathvik-1007/ouroboros that referenced this pull request Apr 17, 2026
…Q00#275) (Q00#325)

When MCP clients send `null` for optional array parameters
(`constraint_violations`, `current_concepts`), `dict.get(key, [])`
returns `None` because the key exists — it just has a null value.
This causes `TypeError: object of type 'NoneType' has no len()` at
the `log.info()` call before the drift engine is even reached.

Replace `.get(key, [])` with `.get(key) or []` to handle both
missing keys and explicit null values.

Closes Q00#275

Co-authored-by: Claude Opus 4.6 <[email protected]>
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.

ouroboros_measure_drift crashes with TypeError: object of type 'NoneType' has no len()

2 participants