Skip to content

feat: add pythonic_check MCP tool for Python idiom analysis#49

Merged
nnennandukwe merged 18 commits into
mainfrom
feat/pythonic-checker-42
Feb 15, 2026
Merged

feat: add pythonic_check MCP tool for Python idiom analysis#49
nnennandukwe merged 18 commits into
mainfrom
feat/pythonic-checker-42

Conversation

@nnennandukwe
Copy link
Copy Markdown
Owner

Summary

  • Adds new pythonic_check MCP tool that analyzes Python code for non-idiomatic patterns
  • Detects 10+ anti-patterns including range(len()), mutable defaults, == None, and more
  • Provides actionable suggestions with Pythonic alternatives
  • Follows existing tool patterns (file_path or source_code input, structured JSON output)

Patterns Detected

Pattern Suggestion
for i in range(len(x)) Use enumerate(x)
for k in d.keys() Iterate for k in d directly
x == None Use x is None
x == True/False Use truthiness if x:
type(x) == Cls Use isinstance(x, Cls)
len(x) == 0 Use not x
def foo(items=[]) Use items=None pattern
result.append() in loop Use list comprehension
if cond: return True else: return False Return condition directly
Bare except: Catch specific exceptions

Test plan

  • 29 new test cases covering all patterns
  • Edge cases: empty files, syntax errors, clean code
  • All 151 tests pass
  • Linting passes (ruff check)
  • Pre-commit hooks pass

🤖 Generated with Claude Code

Add new MCP tool that detects non-idiomatic Python patterns and suggests
Pythonic alternatives. Detects 10+ anti-patterns including:

- range(len()) loops → enumerate()
- dict.keys() iteration → direct dict iteration
- == None comparisons → is None
- Mutable default arguments (common bug source)
- len(x) == 0 → truthiness checks
- type() comparisons → isinstance()
- Redundant boolean returns
- Bare except clauses

Includes 29 test cases covering all patterns and edge cases.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add pythonic_check MCP tool for Python idiom analysis

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds new pythonic_check MCP tool for analyzing Python code idioms
• Detects 10+ anti-patterns including range(len()), mutable defaults, == None
• Provides actionable suggestions with Pythonic alternatives
• Includes 29 comprehensive test cases covering all patterns and edge cases
Diagram
flowchart LR
  A["Python Source Code"] -->|PythonicChecker| B["AST Analysis"]
  B -->|Pattern Detection| C["Loop Patterns"]
  B -->|Pattern Detection| D["Comparison Patterns"]
  B -->|Pattern Detection| E["Mutable Defaults"]
  B -->|Pattern Detection| F["Collection Building"]
  B -->|Pattern Detection| G["Redundant Code"]
  B -->|Pattern Detection| H["Exception Patterns"]
  C --> I["Issues List"]
  D --> I
  E --> I
  F --> I
  G --> I
  H --> I
  I -->|MCP Server| J["JSON Response"]
Loading

Grey Divider

File Changes

1. src/workshop_mcp/pythonic_check/__init__.py ✨ Enhancement +6/-0

Module initialization and public API exports

• Exports public API for the pythonic_check module
• Provides access to PythonicChecker, PythonicIssue, IssueCategory, and Severity classes

src/workshop_mcp/pythonic_check/init.py


2. src/workshop_mcp/pythonic_check/patterns.py ✨ Enhancement +112/-0

Pattern definitions and issue data structures

• Defines Severity enum with INFO, WARNING, ERROR levels
• Defines IssueCategory enum for 7 categories of Python anti-patterns
• Defines PythonicIssue dataclass to represent detected issues
• Provides 20+ pattern message and suggestion constants for all detectable anti-patterns

src/workshop_mcp/pythonic_check/patterns.py


3. src/workshop_mcp/pythonic_check/pythonic_checker.py ✨ Enhancement +557/-0

Core Pythonic code analysis engine with pattern detection

• Implements PythonicChecker class using Astroid for AST analysis
• Detects 10+ anti-patterns: range(len()), dict.keys(), == None, == True/False, type()
 comparisons, len() == 0, mutable defaults, append() in loops, redundant boolean returns, bare
 except clauses
• Provides check_all() method to run all checks and get_summary() for issue statistics
• Supports both file path and source code string inputs with proper error handling

src/workshop_mcp/pythonic_check/pythonic_checker.py


View more (2)
4. src/workshop_mcp/server.py ✨ Enhancement +154/-0

MCP server integration for pythonic_check tool

• Imports PythonicChecker from pythonic_check module
• Adds pythonic_check tool to MCP tools list with JSON schema definition
• Implements _execute_pythonic_check() method with input validation, file path security checks,
 and error handling
• Returns structured JSON response with detected issues, summary, and file information

src/workshop_mcp/server.py


5. tests/test_pythonic_checker.py 🧪 Tests +453/-0

Comprehensive test suite for pythonic checker

• Adds 29 test cases organized into 11 test classes covering all anti-pattern categories
• Tests pattern detection for range(len()), dict.keys(), None/boolean/type comparisons, len()
 checks, mutable defaults, collection building, redundant code, and exception patterns
• Includes edge case tests for empty files, syntax errors, and clean Pythonic code
• Verifies that correct patterns are detected and false positives are avoided

tests/test_pythonic_checker.py


Grey Divider

Qodo Logo


🛠️ Relevant configurations:


These are the relevant configurations for this tool:

[config]

is_auto_command: True
is_new_pr: True
model_reasoning: vertex_ai/gemini-2.5-pro
model: gpt-5.2-2025-12-11
model_turbo: anthropic/claude-haiku-4-5-20251001
fallback_models: ['anthropic/claude-sonnet-4-5-20250929', 'bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0']
second_model_for_exhaustive_mode: o4-mini
git_provider: github
publish_output: True
publish_output_no_suggestions: True
publish_output_progress: True
verbosity_level: 0
publish_logs: False
debug_mode: False
use_wiki_settings_file: True
use_repo_settings_file: True
use_global_settings_file: True
use_global_wiki_settings_file: False
disable_auto_feedback: False
ai_timeout: 150
response_language: en-US
clone_repo_instead_of_fetch: True
always_clone: False
add_repo_metadata: True
clone_repo_time_limit: 300
publish_inline_comments_fallback_batch_size: 5
publish_inline_comments_fallback_sleep_time: 2
max_model_tokens: 32000
custom_model_max_tokens: -1
patch_extension_skip_types: ['.md', '.txt']
extra_allowed_extensions: []
allow_dynamic_context: True
allow_forward_dynamic_context: True
max_extra_lines_before_dynamic_context: 12
patch_extra_lines_before: 5
patch_extra_lines_after: 1
ai_handler: litellm
cli_mode: False
TRIAL_GIT_ORG_MAX_INVOKES_PER_MONTH: 75
TRIAL_RATIO_CLOSE_TO_LIMIT: 0.8
invite_only_mode: False
enable_request_access_msg_on_new_pr: False
check_also_invites_field: False
calculate_context: True
disable_checkboxes: False
output_relevant_configurations: True
large_patch_policy: clip
seed: -1
temperature: 0.2
allow_dynamic_context_ab_testing: False
choose_dynamic_context_ab_testing_ratio: 0.5
ignore_pr_title: ['^\\[Auto\\]', '^Auto']
ignore_pr_target_branches: []
ignore_pr_source_branches: []
ignore_pr_labels: []
ignore_ticket_labels: []
allow_only_specific_folders: []
ignore_pr_authors: []
ignore_repositories: []
ignore_language_framework: []
enable_ai_metadata: True
present_reasoning: True
max_tickets: 10
max_tickets_chars: 8000
prevent_any_approval: False
enable_comment_approval: False
enable_auto_approval: False
auto_approve_for_low_review_effort: -1
auto_approve_for_no_suggestions: False
ensure_ticket_compliance: False
new_diff_format: True
new_diff_format_add_external_references: True
enable_custom_labels: False

[pr_description]

publish_labels: False
add_original_user_description: True
generate_ai_title: False
extra_instructions: 
enable_pr_type: True
final_update_message: True
enable_help_text: False
enable_help_comment: False
bring_latest_tag: False
enable_pr_diagram: True
publish_description_as_comment: False
publish_description_as_comment_persistent: True
enable_semantic_files_types: True
collapsible_file_list: adaptive
collapsible_file_list_threshold: 8
inline_file_summary: False
use_description_markers: False
include_generated_by_header: True
enable_large_pr_handling: True
max_ai_calls: 4
auto_create_ticket: False

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented Jan 29, 2026

Code Review by Qodo

🐞 Bugs (9) 📘 Rule violations (13) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Type comparison check disabled 🐞 Bug ✓ Correctness
Description
_check_type_comparison() is hard-disabled (early return) but the added tests expect an
isinstance()-related issue for type(x) == MyClass, so CI will fail and the tool won’t detect a
documented pattern.
Code

src/workshop_mcp/pythonic_check/pythonic_checker.py[R280-288]

+    def _check_type_comparison(self, node: astroid.Compare) -> None:
+        """Check for type(x) == SomeClass instead of isinstance().
+
+        NOTE: This check is disabled to avoid false positives. The check would flag
+        type(x) == y for any y, but isinstance() is only a valid replacement when y
+        is actually a class/type. Proper detection requires type inference.
+        """
+        # Disabled to prevent false positives when comparing types to non-class values
+        return
Evidence
The implementation returns immediately without appending any issue for type() comparisons, while the
test suite asserts such an issue must be produced for type(x) == MyClass.

src/workshop_mcp/pythonic_check/pythonic_checker.py[280-288]
tests/test_pythonic_checker.py[160-177]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PythonicChecker._check_type_comparison()` is currently disabled (immediate `return`), but the test suite expects this pattern to be detected. This will fail CI and the tool will miss a documented rule.
## Issue Context
A safe implementation likely needs to avoid false positives by confirming the comparator resolves to a class/type (e.g., via `astroid` inference) before suggesting `isinstance`.
## Fix Focus Areas
- src/workshop_mcp/pythonic_check/pythonic_checker.py[280-288]
- tests/test_pythonic_checker.py[160-177]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Dict setitem check disabled 🐞 Bug ✓ Correctness
Description
_check_dict_setitem_in_loop() is hard-disabled (early return) but the added tests expect a “dict
comprehension” issue for result[x] = ... inside a loop, so CI will fail and the tool won’t detect
a documented pattern.
Code

src/workshop_mcp/pythonic_check/pythonic_checker.py[R446-454]

+    def _check_dict_setitem_in_loop(self, node: astroid.For) -> None:
+        """Check for dict[key] = value in a loop that could be a comprehension.
+
+        NOTE: This check is disabled to avoid false positives. Subscript assignments
+        can be for lists, arrays, or other containers, not just dicts. Proper detection
+        would require type inference to determine if the target is actually a dict.
+        """
+        # Disabled to prevent false positives for list/array subscript assignments
+        return
Evidence
The implementation returns immediately without producing issues for subscript assignments in loops,
while the test suite asserts a dict-comprehension suggestion must be produced for such code.

src/workshop_mcp/pythonic_check/pythonic_checker.py[446-454]
tests/test_pythonic_checker.py[314-325]
src/workshop_mcp/pythonic_check/patterns.py[87-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PythonicChecker._check_dict_setitem_in_loop()` is disabled, but the tests expect it to produce an issue. This will fail CI and the tool will miss an advertised rule.
## Issue Context
A low-false-positive approach is to only flag subscript assignments when the container can be proven to be a dict (e.g., assigned from a `Dict` literal in the same scope prior to the loop).
## Fix Focus Areas
- src/workshop_mcp/pythonic_check/pythonic_checker.py[446-454]
- tests/test_pythonic_checker.py[314-325]
- src/workshop_mcp/pythonic_check/patterns.py[87-89]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. File read errors uncaught 🐞 Bug ⛯ Reliability
Description
_execute_pythonic_check reads the validated file path but only catches UnicodeDecodeError;
FileNotFoundError/PermissionError/OSError can escape and potentially crash request handling. This
can happen if the file disappears or becomes unreadable after validation.
Code

src/workshop_mcp/server.py[R655-666]

+        # Read file at the trust boundary using the validated path,
+        # then pass content downstream to avoid a second filesystem access (TOCTOU).
+        if validated_path:
+            logger.info("Executing pythonic check on file: %s", validated_path)
+            try:
+                source_code = Path(validated_path).read_text(encoding="utf-8")
+            except UnicodeDecodeError:
+                logger.warning("UnicodeDecodeError in pythonic_check")
+                return self._error_response(
+                    request_id,
+                    JsonRpcError(-32602, "File is not valid UTF-8"),
+                )
Evidence
The file read is outside the later try/except that handles tool execution; the inner try only
catches UnicodeDecodeError, so other I/O failures will propagate out of the handler.

src/workshop_mcp/server.py[655-669]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pythonic_check` reads file contents but only catches `UnicodeDecodeError`. Other I/O errors can escape the handler.
### Issue Context
Even after `validate_exists`, the file can be deleted/changed or become unreadable before `read_text` runs. This should return a structured JSON-RPC error instead of throwing.
### Fix Focus Areas
- src/workshop_mcp/server.py[655-669]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (8)
✅ 4. __init__ missing return hint 📘 Rule violation ✓ Correctness
Description
PythonicChecker.__init__ is missing an explicit return type annotation, which breaks the “all
functions must be annotated” requirement. • This reduces mypy-friendliness and makes annotation
coverage inconsistent across the new module.
Code

src/workshop_mcp/pythonic_check/pythonic_checker.py[51]

+    def __init__(self, source_code: str | None = None, file_path: str | None = None):
Evidence
Compliance ID 9 requires parameter and return type annotations on all new/modified functions. The
new __init__ signature has parameter annotations but no -> None return annotation.

CLAUDE.md
src/workshop_mcp/pythonic_check/pythonic_checker.py[51-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PythonicChecker.__init__` is missing an explicit return type annotation, violating the project requirement for function-level type hints.
## Issue Context
The codebase requires all functions to include parameter and return type annotations (mypy-friendly). `__init__` should be annotated with `-&amp;amp;amp;amp;amp;amp;amp;gt; None`.
## Fix Focus Areas
- src/workshop_mcp/pythonic_check/pythonic_checker.py[51-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 5. __init__ missing return hint 📘 Rule violation ✓ Correctness
Description
PythonicChecker.__init__ is missing an explicit return type annotation, which breaks the “all
functions must be annotated” requirement. • This reduces mypy-friendliness and makes annotation
coverage inconsistent across the new module.
Code

src/workshop_mcp/pythonic_check/pythonic_checker.py[51]

+    def __init__(self, source_code: str | None = None, file_path: str | None = None):
Evidence
Compliance ID 9 requires parameter and return type annotations on all new/modified functions. The
new __init__ signature has parameter annotations but no -> None return annotation.

CLAUDE.md
src/workshop_mcp/pythonic_check/pythonic_checker.py[51-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PythonicChecker.__init__` is missing an explicit return type annotation, violating the project requirement for function-level type hints.
## Issue Context
The codebase requires all functions to include parameter and return type annotations (mypy-friendly). `__init__` should be annotated with `-&amp;amp;amp;amp;amp;amp;amp;amp;gt; None`.
## Fix Focus Areas
- src/workshop_mcp/pythonic_check/pythonic_checker.py[51-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 6. Empty file_path can crash 📘 Rule violation ⛯ Reliability
Description
PythonicChecker.__init__ treats a falsey-but-not-None file_path (e.g., "") as “not
provided” and does not load source_code, leaving source_code as None. • The next lines call
source_code.splitlines() and astroid.parse(source_code, ...), which can raise at runtime; this
is an unhandled edge case.
Code

src/workshop_mcp/pythonic_check/pythonic_checker.py[R64-82]

+        if source_code is None and file_path is None:
+            raise ValueError("Either source_code or file_path must be provided")
+
+        if file_path:
+            path = Path(file_path)
+            if not path.exists():
+                raise FileNotFoundError(f"File not found: {file_path}")
+            source_code = path.read_text(encoding="utf-8")
+            self.file_path = str(file_path)
+        else:
+            self.file_path = None
+
+        self.source_code = source_code
+        self._source_lines = source_code.splitlines()
+
+        try:
+            self.tree = astroid.parse(source_code, path=file_path)
+        except astroid.AstroidSyntaxError as e:
+            raise SyntaxError(str(e)) from e
Evidence
Compliance ID 3 requires explicit handling of edge cases/boundary inputs. The constructor only
checks for both values being None, but later assumes source_code is a str, creating a crash
path when file_path is provided as an empty string.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/workshop_mcp/pythonic_check/pythonic_checker.py[64-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PythonicChecker.__init__` can crash when `file_path` is provided but falsey (e.g., empty string), because `source_code` remains `None` and is used as if it were a `str`.
## Issue Context
The checker is usable directly (not only via the server wrapper), so it must defend against boundary values and maintain safe invariants (`source_code` is always a `str` after validation).
## Fix Focus Areas
- src/workshop_mcp/pythonic_check/pythonic_checker.py[64-82]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 7. Empty file_path can crash 📘 Rule violation ⛯ Reliability
Description
PythonicChecker.__init__ treats a falsey-but-not-None file_path (e.g., "") as “not
provided” and does not load source_code, leaving source_code as None. • The next lines call
source_code.splitlines() and astroid.parse(source_code, ...), which can raise at runtime; this
is an unhandled edge case.
Code

src/workshop_mcp/pythonic_check/pythonic_checker.py[R64-82]

+        if source_code is None and file_path is None:
+            raise ValueError("Either source_code or file_path must be provided")
+
+        if file_path:
+            path = Path(file_path)
+            if not path.exists():
+                raise FileNotFoundError(f"File not found: {file_path}")
+            source_code = path.read_text(encoding="utf-8")
+            self.file_path = str(file_path)
+        else:
+            self.file_path = None
+
+        self.source_code = source_code
+        self._source_lines = source_code.splitlines()
+
+        try:
+            self.tree = astroid.parse(source_code, path=file_path)
+        except astroid.AstroidSyntaxError as e:
+            raise SyntaxError(str(e)) from e
Evidence
Compliance ID 3 requires explicit handling of edge cases/boundary inputs. The constructor only
checks for both values being None, but later assumes source_code is a str, creating a crash
path when file_path is provided as an empty string.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/workshop_mcp/pythonic_check/pythonic_checker.py[64-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PythonicChecker.__init__` can crash when `file_path` is provided but falsey (e.g., empty string), because `source_code` remains `None` and is used as if it were a `str`.
## Issue Context
The checker is usable directly (not only via the server wrapper), so it must defend against boundary values and maintain safe invariants (`source_code` is always a `str` after validation).
## Fix Focus Areas
- src/workshop_mcp/pythonic_check/pythonic_checker.py[64-82]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 8. source_code type not validated 📘 Rule violation ⛨ Security
Description
• The pythonic_check tool validates file_path is a str, but does not validate that
source_code is a str when provided. • Because this is an external tool entrypoint, unvalidated
source_code types can trigger unexpected exceptions and degrade robustness/security posture.
Code

src/workshop_mcp/server.py[R610-631]

+        file_path = arguments.get("file_path")
+        source_code = arguments.get("source_code")
+
+        # Validate that exactly one is provided
+        if not file_path and not source_code:
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Either file_path or source_code must be provided"),
+            )
+        if file_path and source_code:
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Provide only one of file_path or source_code"),
+            )
+
+        # Type check file_path before path validation
+        if file_path is not None and not isinstance(file_path, str):
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "file_path must be a string"),
+            )
+
Evidence
Compliance ID 6 requires validation of external inputs. The handler validates file_path type but
does not validate source_code type before passing it to PythonicChecker, which expects a
string-like object and will operate on it immediately.

Rule 6: Generic: Security-First Input Validation and Data Handling
src/workshop_mcp/server.py[610-631]
src/workshop_mcp/server.py[646-648]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `pythonic_check` handler does not validate the type of externally-provided `source_code` before using it.
## Issue Context
This handler is a public tool interface (JSON-RPC). Input validation is required to prevent unexpected crashes and unsafe behavior.
## Fix Focus Areas
- src/workshop_mcp/server.py[610-631]
- src/workshop_mcp/server.py[646-648]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 9. source_code type not validated 📘 Rule violation ⛨ Security
Description
• The pythonic_check tool validates file_path is a str, but does not validate that
source_code is a str when provided. • Because this is an external tool entrypoint, unvalidated
source_code types can trigger unexpected exceptions and degrade robustness/security posture.
Code

src/workshop_mcp/server.py[R610-631]

+        file_path = arguments.get("file_path")
+        source_code = arguments.get("source_code")
+
+        # Validate that exactly one is provided
+        if not file_path and not source_code:
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Either file_path or source_code must be provided"),
+            )
+        if file_path and source_code:
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Provide only one of file_path or source_code"),
+            )
+
+        # Type check file_path before path validation
+        if file_path is not None and not isinstance(file_path, str):
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "file_path must be a string"),
+            )
+
Evidence
Compliance ID 6 requires validation of external inputs. The handler validates file_path type but
does not validate source_code type before passing it to PythonicChecker, which expects a
string-like object and will operate on it immediately.

Rule 6: Generic: Security-First Input Validation and Data Handling
src/workshop_mcp/server.py[610-631]
src/workshop_mcp/server.py[646-648]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `pythonic_check` handler does not validate the type of externally-provided `source_code` before using it.
## Issue Context
This handler is a public tool interface (JSON-RPC). Input validation is required to prevent unexpected crashes and unsafe behavior.
## Fix Focus Areas
- src/workshop_mcp/server.py[610-631]
- src/workshop_mcp/server.py[646-648]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Discarded validated path 🐞 Bug ⛨ Security
Description
• The server calls PathValidator.validate_exists() (which resolves/canonicalizes and returns a
validated Path) but discards the returned value. • It then re-opens the user-provided file_path
string inside PythonicChecker, creating a TOCTOU gap (validated path vs. used path) and weakening
the security guarantee provided by canonicalization/symlink resolution.
Code

src/workshop_mcp/server.py[R633-646]

+        if file_path:
+            try:
+                self.path_validator.validate_exists(file_path, must_be_file=True)
+            except PathValidationError as e:
+                return self._error_response(
+                    request_id,
+                    JsonRpcError(-32602, str(e)),
+                )
+
+        try:
+            if file_path:
+                logger.info("Executing pythonic check on file: %s", file_path)
+                checker = PythonicChecker(file_path=file_path)
+            else:
Evidence
validate_exists() resolves and returns a canonical Path under allowed roots; the server ignores
that and later reads using the raw input string, so the actual file used may differ from the
validated one if the filesystem changes between validation and read (e.g., symlink swap).

src/workshop_mcp/security/path_validator.py[102-132]
src/workshop_mcp/security/path_validator.py[154-178]
src/workshop_mcp/server.py[633-646]
src/workshop_mcp/pythonic_check/pythonic_checker.py[67-72]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The server validates `file_path` but discards the returned canonical `Path` and later re-reads using the original string. This creates a TOCTOU gap and weakens the guarantees provided by canonicalized validation.
### Issue Context
`PathValidator.validate()` explicitly resolves the path (including symlink resolution) before checking containment under allowed roots.
### Fix Focus Areas
- src/workshop_mcp/server.py[632-646]
- src/workshop_mcp/security/path_validator.py[102-132]
- src/workshop_mcp/pythonic_check/pythonic_checker.py[67-72]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Discarded validated path 🐞 Bug ⛨ Security
Description
• The server calls PathValidator.validate_exists() (which resolves/canonicalizes and returns a
validated Path) but discards the returned value. • It then re-opens the user-provided file_path
string inside PythonicChecker, creating a TOCTOU gap (validated path vs. used path) and weakening
the security guarantee provided by canonicalization/symlink resolution.
Code

src/workshop_mcp/server.py[R633-646]

+        if file_path:
+            try:
+                self.path_validator.validate_exists(file_path, must_be_file=True)
+            except PathValidationError as e:
+                return self._error_response(
+                    request_id,
+                    JsonRpcError(-32602, str(e)),
+                )
+
+        try:
+            if file_path:
+                logger.info("Executing pythonic check on file: %s", file_path)
+                checker = PythonicChecker(file_path=file_path)
+            else:
Evidence
validate_exists() resolves and returns a canonical Path under allowed roots; the server ignores
that and later reads using the raw input string, so the actual file used may differ from the
validated one if the filesystem changes between validation and read (e.g., symlink swap).

src/workshop_mcp/security/path_validator.py[102-132]
src/workshop_mcp/security/path_validator.py[154-178]
src/workshop_mcp/server.py[633-646]
src/workshop_mcp/pythonic_check/pythonic_checker.py[67-72]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The server validates `file_path` but discards the returned canonical `Path` and later re-reads using the original string. This creates a TOCTOU gap and weakens the guarantees provided by canonicalized validation.
### Issue Context
`PathValidator.validate()` explicitly resolves the path (including symlink resolution) before checking containment under allowed roots.
### Fix Focus Areas
- src/workshop_mcp/server.py[632-646]
- src/workshop_mcp/security/path_validator.py[102-132]
- src/workshop_mcp/pythonic_check/pythonic_checker.py[67-72]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

✅ 12. Raw validation errors returned 📘 Rule violation ⛨ Security
Description
• The handler returns str(e) / str(exc) from path/security validation errors directly to the
client, which can leak internal filesystem/security details. • Compliance requires user-facing
errors to be generic while detailed context is kept in secure logs for debugging.
Code

src/workshop_mcp/server.py[R633-640]

+        if file_path:
+            try:
+                self.path_validator.validate_exists(file_path, must_be_file=True)
+            except PathValidationError as e:
+                return self._error_response(
+                    request_id,
+                    JsonRpcError(-32602, str(e)),
+                )
Evidence
Compliance ID 4 requires avoiding exposure of internal implementation details via client-facing
error messages. The new handler directly propagates exception strings into JsonRpcError messages.

Rule 4: Generic: Secure Error Handling
src/workshop_mcp/server.py[633-640]
src/workshop_mcp/server.py[711-716]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Client-facing responses currently include raw exception messages from validation/security checks, which can reveal internal details.
## Issue Context
Secure error handling requires generic user-facing messages and detailed internal logs.
## Fix Focus Areas
- src/workshop_mcp/server.py[633-640]
- src/workshop_mcp/server.py[711-716]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 13. Wrong tool name in logs 🐞 Bug ✓ Correctness
Description
keyword_search and performance_check now log validation failures as if they happened in
pythonic_check, which will mislead debugging/alerting. The same blocks also changed client-visible
SecurityValidationError messages to a generic string even though these exceptions are intended to be
safe to expose.
Code

src/workshop_mcp/server.py[R461-465]

+            logger.warning("Security validation error in pythonic_check: %s", exc)
        return self._error_response(
            request_id,
-                JsonRpcError(-32602, str(exc)),
+                JsonRpcError(-32602, "Security validation failed"),
        )
Evidence
In keyword_search, the SecurityValidationError handler logs the wrong tool name. In
performance_check, both PathValidationError and SecurityValidationError handlers also log the wrong
tool name. Additionally, SecurityValidationError explicitly documents that its string form is safe
to expose, so changing responses to a generic message reduces client usefulness unnecessarily (and
is inconsistent with the module’s stated contract).

src/workshop_mcp/server.py[440-465]
src/workshop_mcp/server.py[508-517]
src/workshop_mcp/server.py[588-593]
src/workshop_mcp/security/exceptions.py[1-13]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`keyword_search` and `performance_check` log validation errors using the wrong tool name (&amp;amp;amp;amp;quot;pythonic_check&amp;amp;amp;amp;quot;), and they also return a generic `Security validation failed` message even though `SecurityValidationError` messages are documented as safe to expose.
### Issue Context
This harms observability (misleading logs/metrics) and can degrade client UX by hiding useful, safe-to-expose error details.
### Fix Focus Areas
- src/workshop_mcp/server.py[440-465]
- src/workshop_mcp/server.py[508-517]
- src/workshop_mcp/server.py[588-593]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 14. Exception details logged verbatim 📘 Rule violation ⛨ Security
Description
• The handler logs exception objects (%s, exc) for input-related failures, which may include
user-provided content (e.g., parts of source_code in SyntaxError messages) and potentially
sensitive data. • This conflicts with the requirement that logs must not include sensitive
information and should be safe for auditing.
Code

src/workshop_mcp/server.py[R687-701]

+        except ValueError as exc:
+            logger.warning("ValueError in pythonic_check: %s", exc)
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Invalid parameters"),
+            )
+        except FileNotFoundError as exc:
+            logger.warning("FileNotFoundError in pythonic_check: %s", exc)
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Resource not found"),
+            )
+        except SyntaxError as exc:
+            logger.warning("SyntaxError in pythonic_check: %s", exc)
+            return self._error_response(
Evidence
Compliance ID 5 requires avoiding sensitive data in logs. Logging raw exception strings for
SyntaxError/ValueError can inadvertently include user-provided code fragments or other sensitive
details.

Rule 5: Generic: Secure Logging Practices
src/workshop_mcp/server.py[687-704]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The handler logs raw exception text for user-controlled failures, which can inadvertently capture sensitive content in logs.
## Issue Context
Logging should remain useful for debugging while preventing sensitive data exposure.
## Fix Focus Areas
- src/workshop_mcp/server.py[687-704]
- src/workshop_mcp/server.py[711-716]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (8)
✅ 15. Bad mutable-default advice 🐞 Bug ✓ Correctness
Description
Mutable default argument suggestions use x = x or []/{}/set() which is not equivalent to the safe
if x is None: x = ... pattern and can change semantics when callers pass empty collections. This
can lead users to introduce subtle bugs by following the tool’s recommendation.
Code

src/workshop_mcp/pythonic_check/patterns.py[R91-98]

+MUTABLE_DEFAULT_LIST_MSG = "Avoid mutable default argument (list)"
+MUTABLE_DEFAULT_LIST_SUGGESTION = "def foo(items=None): items = items or []"
+
+MUTABLE_DEFAULT_DICT_MSG = "Avoid mutable default argument (dict)"
+MUTABLE_DEFAULT_DICT_SUGGESTION = "def foo(d=None): d = d or {}"
+
+MUTABLE_DEFAULT_SET_MSG = "Avoid mutable default argument (set)"
+MUTABLE_DEFAULT_SET_SUGGESTION = "def foo(s=None): s = s or set()"
Evidence
The suggestion strings explicitly recommend the or-fallback pattern, which replaces intentionally
provided empty containers and is a known Python pitfall. These strings are directly returned to
users as the suggested fix.

src/workshop_mcp/pythonic_check/patterns.py[90-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Mutable-default suggestions currently recommend `x = x or []/{}/set()`, which can override intentionally passed empty containers.
### Issue Context
The tool’s suggestions should be correctness-preserving to avoid guiding users into subtle behavior changes.
### Fix Focus Areas
- src/workshop_mcp/pythonic_check/patterns.py[90-99]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 16. Tests missing type annotations 📘 Rule violation ✓ Correctness
Description
• New test methods are added without parameter/return type annotations (e.g., missing -> None),
which violates the project-wide type-hint requirement. • Even in tests, missing annotations reduce
consistency and can undermine strict mypy settings if tests are type-checked.
Code

tests/test_pythonic_checker.py[R11-12]

+    def test_detects_range_len(self):
+        """Should detect for i in range(len(items)) pattern."""
Evidence
Compliance ID 9 requires all new/modified functions to include type annotations. The new test
functions/methods are unannotated.

CLAUDE.md
tests/test_pythonic_checker.py[11-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New tests add many unannotated functions/methods, conflicting with the requirement for full function-level type hints.
## Issue Context
If the repository enforces type checking on tests (or aims for consistent annotation coverage), tests should also be annotated.
## Fix Focus Areas
- tests/test_pythonic_checker.py[11-453]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 17. Raw validation errors returned 📘 Rule violation ⛨ Security
Description
• The handler returns str(e) / str(exc) from path/security validation errors directly to the
client, which can leak internal filesystem/security details. • Compliance requires user-facing
errors to be generic while detailed context is kept in secure logs for debugging.
Code

src/workshop_mcp/server.py[R633-640]

+        if file_path:
+            try:
+                self.path_validator.validate_exists(file_path, must_be_file=True)
+            except PathValidationError as e:
+                return self._error_response(
+                    request_id,
+                    JsonRpcError(-32602, str(e)),
+                )
Evidence
Compliance ID 4 requires avoiding exposure of internal implementation details via client-facing
error messages. The new handler directly propagates exception strings into JsonRpcError messages.

Rule 4: Generic: Secure Error Handling
src/workshop_mcp/server.py[633-640]
src/workshop_mcp/server.py[711-716]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Client-facing responses currently include raw exception messages from validation/security checks, which can reveal internal details.
## Issue Context
Secure error handling requires generic user-facing messages and detailed internal logs.
## Fix Focus Areas
- src/workshop_mcp/server.py[633-640]
- src/workshop_mcp/server.py[711-716]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 18. Exception details logged verbatim 📘 Rule violation ⛨ Security
Description
• The handler logs exception objects (%s, exc) for input-related failures, which may include
user-provided content (e.g., parts of source_code in SyntaxError messages) and potentially
sensitive data. • This conflicts with the requirement that logs must not include sensitive
information and should be safe for auditing.
Code

src/workshop_mcp/server.py[R687-701]

+        except ValueError as exc:
+            logger.warning("ValueError in pythonic_check: %s", exc)
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Invalid parameters"),
+            )
+        except FileNotFoundError as exc:
+            logger.warning("FileNotFoundError in pythonic_check: %s", exc)
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Resource not found"),
+            )
+        except SyntaxError as exc:
+            logger.warning("SyntaxError in pythonic_check: %s", exc)
+            return self._error_response(
Evidence
Compliance ID 5 requires avoiding sensitive data in logs. Logging raw exception strings for
SyntaxError/ValueError can inadvertently include user-provided code fragments or other sensitive
details.

Rule 5: Generic: Secure Logging Practices
src/workshop_mcp/server.py[687-704]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The handler logs raw exception text for user-controlled failures, which can inadvertently capture sensitive content in logs.
## Issue Context
Logging should remain useful for debugging while preventing sensitive data exposure.
## Fix Focus Areas
- src/workshop_mcp/server.py[687-704]
- src/workshop_mcp/server.py[711-716]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 19. Tests missing type annotations 📘 Rule violation ✓ Correctness
Description
• New test methods are added without parameter/return type annotations (e.g., missing -> None),
which violates the project-wide type-hint requirement. • Even in tests, missing annotations reduce
consistency and can undermine strict mypy settings if tests are type-checked.
Code

tests/test_pythonic_checker.py[R11-12]

+    def test_detects_range_len(self):
+        """Should detect for i in range(len(items)) pattern."""
Evidence
Compliance ID 9 requires all new/modified functions to include type annotations. The new test
functions/methods are unannotated.

CLAUDE.md
tests/test_pythonic_checker.py[11-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New tests add many unannotated functions/methods, conflicting with the requirement for full function-level type hints.
## Issue Context
If the repository enforces type checking on tests (or aims for consistent annotation coverage), tests should also be annotated.
## Fix Focus Areas
- tests/test_pythonic_checker.py[11-453]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 20. Schema line may exceed 100 📘 Rule violation ✓ Correctness
Description
• The new tool schema includes a long string literal on one line that appears to exceed the
100-character line-length standard. • This can introduce Ruff formatting/lint violations under the
repository’s configured constraints.
Code

src/workshop_mcp/server.py[R309-312]

+                            "source_code": {
+                                "type": "string",
+                                "description": "Optional Python source code string to analyze instead of file",
+                            },
Evidence
Compliance ID 8 requires keeping changed lines within the 100-character limit (unless intentionally
configured). The added schema field uses a long single-line string that is likely over the limit due
to indentation plus string length.

CLAUDE.md
src/workshop_mcp/server.py[309-312]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A newly added schema description line likely exceeds the 100-character limit.
## Issue Context
The project enforces Ruff formatting with a 100-character line-length standard.
## Fix Focus Areas
- src/workshop_mcp/server.py[309-312]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


21. Empty source rejected 🐞 Bug ✓ Correctness
Description
• The server uses truthiness (if not file_path and not source_code) to validate presence, so
source_code="" is treated as missing. • This contradicts the checker’s behavior (empty source is
valid and returns no issues) and the unit test expectations, so clients cannot run pythonic_check
on an empty buffer/file via MCP.
Code

src/workshop_mcp/server.py[R613-618]

+        # Validate that exactly one is provided
+        if not file_path and not source_code:
+            return self._error_response(
+                request_id,
+                JsonRpcError(-32602, "Either file_path or source_code must be provided"),
+            )
Evidence
The server rejects empty-string source_code inputs, while the checker and tests demonstrate that
empty source should be accepted and yield an empty issue list.

src/workshop_mcp/server.py[613-618]
tests/test_pythonic_checker.py[419-425]
src/workshop_mcp/pythonic_check/pythonic_checker.py[64-78]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The server checks `if not file_path and not source_code`, which treats `source_code=&amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;quot;` as missing. This prevents analyzing empty source code even though `PythonicChecker` supports it.
### Issue Context
Unit tests validate that `PythonicChecker(source_code=&amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;quot;)` is valid and returns no issues.
### Fix Focus Areas
- src/workshop_mcp/server.py[610-624]
- src/workshop_mcp/server.py[642-649]
- tests/test_pythonic_checker.py[419-425]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 22. UTF-8 decode yields 500 🐞 Bug ⛯ Reliability
Description
PythonicChecker reads file_path using UTF-8 without handling UnicodeDecodeError. • When this
happens via MCP, it is caught by the generic exception handler and returned as `-32603 Internal
error instead of a client-facing -32602` error (e.g., "file is not valid UTF-8").
Code

src/workshop_mcp/pythonic_check/pythonic_checker.py[R67-72]

+        if file_path:
+            path = Path(file_path)
+            if not path.exists():
+                raise FileNotFoundError(f"File not found: {file_path}")
+            source_code = path.read_text(encoding="utf-8")
+            self.file_path = str(file_path)
Evidence
The checker unconditionally decodes files as UTF-8, and the server does not have a specific handler
for decode errors, so the response becomes an internal error.

src/workshop_mcp/pythonic_check/pythonic_checker.py[67-72]
src/workshop_mcp/server.py[717-726]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Reading a file as UTF-8 can raise `UnicodeDecodeError`. Today this becomes a generic `-32603 Internal error` from the MCP server.
### Issue Context
The tool schema allows arbitrary `file_path` inputs (within allowed roots). Non-UTF8 files are possible.
### Fix Focus Areas
- src/workshop_mcp/pythonic_check/pythonic_checker.py[67-83]
- src/workshop_mcp/server.py[642-726]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread src/workshop_mcp/pythonic_check/pythonic_checker.py Outdated
Comment thread src/workshop_mcp/pythonic_check/pythonic_checker.py
Comment thread src/workshop_mcp/server.py
Comment thread src/workshop_mcp/server.py Outdated
@nnennandukwe
Copy link
Copy Markdown
Owner Author

nnennandukwe commented Jan 29, 2026

Claude Code Opus 4.5 - Code review

Found 1 issue:

  1. String concatenation detection only catches literals, missing variables (bug)

The _check_string_concat_in_loop method checks isinstance(stmt.value, astroid.Const) and isinstance(stmt.value.value, str) which only detects string literal concatenation like result += "text". It misses the more common and equally problematic case of variable concatenation like result += item_name. Both patterns have identical O(n^2) performance implications due to string immutability.

# Check if value being concatenated is a string literal
if isinstance(stmt.value, astroid.Const) and isinstance(stmt.value.value, str):
self._issues.append(

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Repository owner deleted a comment from chatgpt-codex-connector Bot Jan 29, 2026
Repository owner deleted a comment from chatgpt-codex-connector Bot Jan 29, 2026
@nnennandukwe
Copy link
Copy Markdown
Owner Author

nnennandukwe commented Jan 30, 2026

Codex CLI GPT 5.2 Medium - Code Review Summary

The pythonic_check tool currently mishandles empty source_code, lacks type validation for source_code, and emits misleading diagnostics for non-dict subscript
assignments.


Findings

P2 — Accept empty source_code as valid input

Location: src/workshop_mcp/server.py:613-618
Issue: The handler checks if not file_path and not source_code, so source_code: "" is treated as missing and rejected.
Impact: Valid empty buffers are rejected even though the schema allows empty strings and PythonicChecker can handle them.
Fix idea: Treat empty string as valid input; only reject when source_code is None and file_path is missing.


P2 — Validate source_code type to avoid internal errors

Location: src/workshop_mcp/server.py:610-612
Issue: file_path is type-checked, but source_code is not.
Impact: Non-string input (e.g., list/number) triggers an internal error (AttributeError on .splitlines()), returning a 500 instead of -32602.
Fix idea: Validate source_code is a string and return -32602 for invalid types.


P3 — Avoid dict-comprehension suggestion for all subscript assigns

Location: src/workshop_mcp/pythonic_check/pythonic_checker.py:464-472
Issue: Any subscript assignment inside a loop is flagged with a dict-comprehension suggestion.
Impact: Misleading output for list indexing, numpy arrays, or other non-dict targets.
Fix idea: Gate the rule to known dict targets (e.g., variables assigned {} or dict()).


Generated by Codex.

nnennandukwe added a commit that referenced this pull request Feb 2, 2026
PathValidator.validate() and validate_exists() return resolved Path
objects after symlink resolution, but the returned values were being
discarded. The code then used the original user-provided strings for
file operations, creating a Time-of-Check-Time-of-Use vulnerability.

Changes:
- keyword_search: capture validated_paths from validate_multiple()
  and use validated_path_strings for tool execution
- performance_check: capture validated_path from validate_exists()
  and use validated_file_path for PerformanceChecker and result output

Addresses PR #49 review feedback.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add `-> None` return type annotation to comply with project
requirement that all functions include type hints.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@nnennandukwe
Copy link
Copy Markdown
Owner Author

Codex CLI GPT-5.3 Code Review Summary

Thanks for the addition of pythonic_check and the accompanying checker/test suite. I reviewed the PR diff against origin/main and found a few issues worth addressing before merge.

Findings

  1. High: source_code input is not type-validated in MCP handler
  • File: src/workshop_mcp/server.py:610, src/workshop_mcp/server.py:614, src/workshop_mcp/server.py:648, src/workshop_mcp/server.py:717
  • Issue: source_code is never checked to be a string. Invalid input (e.g. number/object) falls through and can produce a -32603 Internal error
    instead of -32602 Invalid params.
  • Why it matters: This is an API contract/validation regression for tool calls.
  1. Medium: Empty source_code is treated as missing input
  • File: src/workshop_mcp/server.py:614
  • Issue: Presence checks use truthiness (if not file_path and not source_code), so source_code="" is rejected even though empty Python source is
    valid and checker supports it.
  • Why it matters: Legit valid input is incorrectly rejected.
  1. Medium: False positives for dict-comprehension suggestion
  • File: src/workshop_mcp/pythonic_check/pythonic_checker.py:464, src/workshop_mcp/pythonic_check/pythonic_checker.py:471
  • Issue: Any subscript assignment in a loop is flagged as “use dict comprehension,” including non-dict cases like list/index updates (arr[i] = ...).
  • Why it matters: Produces incorrect guidance and noisy results.
  1. Low: Mutable default checker misses set() calls
  • File: src/workshop_mcp/pythonic_check/pythonic_checker.py:395
  • Related test note: tests/test_pythonic_checker.py:271
  • Issue: The checker detects set literals but not set() as a default argument.
  • Why it matters: Common mutable-default pitfall goes undetected.

———

Test Coverage Gaps

  • No server protocol tests were added for the new pythonic_check tool path (dispatch + validation + error mapping).
  • Recommended to add tests covering:
    • call_tool with name="pythonic_check"
    • invalid source_code types -> -32602
    • empty source_code accepted
    • mutually exclusive file_path/source_code behavior
    • unknown tool and schema mismatch behavior consistency

———

Suggested Fixes

  1. In _execute_pythonic_check, switch presence checks to explicit is None logic.
  2. Add explicit type check for source_code (str).
  3. Restrict dict-setitem detection to likely dict targets (or infer target type before flagging).
  4. Extend mutable-default detection to include call-form mutables like list(), dict(), set().
  5. Add protocol tests for the new MCP tool handler path.

nnennandukwe and others added 10 commits February 10, 2026 15:59
Resolves Qodo review comment #2743700464
Priority: P0 (Security)

PR: #49

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Entire-Checkpoint: 0bdb3ab1f37c
Resolves Qodo review comment #2743700467
Priority: P0 (Security)

PR: #49

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Entire-Checkpoint: 0bdb3ab1f37c
Resolves Qodo review comment #2743700461
Priority: P1 (Reliability)

PR: #49

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Entire-Checkpoint: 0bdb3ab1f37c
Move file I/O from PythonicChecker into the server handler, immediately
after path validation. The validated canonical Path is now used for a
single filesystem read, and only the content string is passed downstream.
This eliminates the second filesystem access that created a window for
symlink races between validation and use.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Entire-Checkpoint: d14b1011d0d5
- Replace raw PathValidationError messages with generic "Invalid file path"
- Replace raw SecurityValidationError messages with generic "Security validation failed"
- Log detailed errors internally for debugging while keeping client-facing messages safe
- Prevents exposure of internal filesystem/security details to clients

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: c6f29cedfcc4
- Remove exception details from ValueError and SyntaxError logs (may contain user code)
- Remove exception details from FileNotFoundError and KeyError logs
- Log only exception type to remain useful for debugging
- Prevents user-provided code fragments or sensitive details from appearing in logs

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: c6f29cedfcc4
- Add -> None return type annotations to all test methods
- Ensures consistency with project-wide type-hint requirement
- Improves mypy coverage even for test code

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: c6f29cedfcc4
- Reduce source_code description from 111 to under 100 characters
- Maintains clarity while meeting Ruff formatting standards
- Changed from "Optional Python source code string to analyze instead of file" to "Python source code string to analyze"

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: c6f29cedfcc4
- Change from truthiness checks to explicit is None checks
- Allows source_code="" to be treated as valid empty source
- PythonicChecker supports empty source and returns no issues
- Fixes rejection of valid empty buffers/files

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: c6f29cedfcc4
- Catch UnicodeDecodeError when reading file as UTF-8
- Return proper -32602 client error instead of -32603 internal error
- Non-UTF8 files are possible within allowed roots and should be handled gracefully
- Provides clear error message: "File is not valid UTF-8"

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: c6f29cedfcc4
@nnennandukwe
Copy link
Copy Markdown
Owner Author

Qodo Fix Summary

Reviewed and addressed Qodo review issues:

✅ Fixed Issues

Security (MEDIUM priority)

  • Raw validation errors returned (Issue Fix/async exceptions #5) - Now returns generic "Invalid file path" and "Security validation failed" messages to prevent exposure of internal filesystem/security details
  • Exception details logged verbatim (Issue fix: clean up error handling #6) - Removed exception details from logs for ValueError/SyntaxError/FileNotFoundError/KeyError to prevent sensitive data exposure

Correctness (MEDIUM/LOW priority)

ℹ️ Already Resolved (in previous commits)


Generated by Qodo PR Resolver skill via Claude Code

@nnennandukwe
Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 8780a45

Comment on lines +655 to +666
# Read file at the trust boundary using the validated path,
# then pass content downstream to avoid a second filesystem access (TOCTOU).
if validated_path:
logger.info("Executing pythonic check on file: %s", validated_path)
try:
source_code = Path(validated_path).read_text(encoding="utf-8")
except UnicodeDecodeError:
logger.warning("UnicodeDecodeError in pythonic_check")
return self._error_response(
request_id,
JsonRpcError(-32602, "File is not valid UTF-8"),
)
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.

Action required

1. File read errors uncaught 🐞 Bug ⛯ Reliability

_execute_pythonic_check reads the validated file path but only catches UnicodeDecodeError;
FileNotFoundError/PermissionError/OSError can escape and potentially crash request handling. This
can happen if the file disappears or becomes unreadable after validation.
Agent Prompt
### Issue description
`pythonic_check` reads file contents but only catches `UnicodeDecodeError`. Other I/O errors can escape the handler.

### Issue Context
Even after `validate_exists`, the file can be deleted/changed or become unreadable before `read_text` runs. This should return a structured JSON-RPC error instead of throwing.

### Fix Focus Areas
- src/workshop_mcp/server.py[655-669]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

…Error

- Catch OSError (base class for FileNotFoundError, PermissionError, IsADirectoryError, etc.)
- File can be deleted, made unreadable, or changed between validation and read
- Returns structured -32602 error instead of throwing -32603 internal error
- Logs exception type for debugging without exposing details

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: 0d3929a5b62c
@nnennandukwe
Copy link
Copy Markdown
Owner Author

Additional Fix Applied

I/O error handling gap closed - Added OSError catch to handle all I/O errors (FileNotFoundError, PermissionError, IsADirectoryError, etc.) that can occur between validation and file read.

Even after validate_exists() passes, the file can be:

  • Deleted before read_text() runs
  • Changed to a directory
  • Made unreadable (permissions changed)

Now returns structured -32602 error instead of throwing -32603 internal error.

Commit: 12a3d84 - fix: handle all I/O errors when reading files, not just UnicodeDecodeError


Generated by Claude Code

nnennandukwe and others added 2 commits February 15, 2026 09:05
- Capture and use validated path instead of discarding it (closes TOCTOU gap)
- Fix wrong tool name in logs (was "pythonic_check", now "performance_check")
- Add OSError and UnicodeDecodeError handling when reading files
- Read file at trust boundary and pass source_code to avoid second filesystem access

Fixes Qodo issues #8, #9, #11 (part of #1)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: e8eac6ac9624
- Replace raw PathValidationError message with generic "Invalid file path"
- Log detailed error internally for debugging while keeping client-facing message safe
- Prevents exposure of internal filesystem/security details to clients

Fixes Qodo issue #8/#9 for keyword_search handler

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: e8eac6ac9624
nnennandukwe and others added 3 commits February 15, 2026 09:06
- Change from unsafe "x = x or []" pattern to safe "if x is None: x = []"
- Prevents semantic changes when callers pass empty collections
- Applies to list, dict, and set mutable default suggestions

The "x = x or []" pattern incorrectly creates a new collection when caller
passes an empty one ([], {}, or set()), changing semantics. The safe pattern
only creates a new collection when None is passed.

Fixes Qodo issue #13

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: e8eac6ac9624
- Disable _check_dict_setitem_in_loop to avoid false positives
- Check was flagging ALL subscript assignments (lists, arrays, etc.), not just dicts
- Proper detection requires type inference which is complex
- Better to avoid false positives than suggest incorrect dict comprehensions

Fixes Qodo issue #21

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: e8eac6ac9624
- Disable _check_type_comparison to avoid incorrect isinstance() suggestions
- Check was flagging all type(x) == y comparisons regardless of whether y is a class
- isinstance() is only valid when RHS is actually a class/type
- Proper detection requires type inference which is complex

Fixes Qodo issue #22

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Entire-Checkpoint: e8eac6ac9624
@nnennandukwe
Copy link
Copy Markdown
Owner Author

Qodo Fix Summary - Round 2

Fixed all 8 outstanding issues from the persistent Qodo review:

✅ Fixed Issues

CRITICAL (Security/Reliability)

MEDIUM (Correctness)

LOW (False Positives)

📊 Commits

1fb129f fix: apply security fixes to performance_check handler
1ab2ffe fix: return generic validation error message in keyword_search handler
8767336 fix: use safe pattern for mutable default suggestions
0ac58da fix: disable subscript rule to prevent false positives
bd8a190 fix: disable type() comparison rule to prevent false positives

All fixes follow the same security and correctness patterns established in the initial round of fixes.


Generated by Qodo PR Resolver skill via Claude Code

@nnennandukwe
Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit bd8a190

Comment thread src/workshop_mcp/pythonic_check/pythonic_checker.py
Comment thread src/workshop_mcp/pythonic_check/pythonic_checker.py
@nnennandukwe nnennandukwe merged commit 2553d8b into main Feb 15, 2026
1 check passed
nnennandukwe added a commit that referenced this pull request Feb 15, 2026
PathValidator.validate() and validate_exists() return resolved Path
objects after symlink resolution, but the returned values were being
discarded. The code then used the original user-provided strings for
file operations, creating a Time-of-Check-Time-of-Use vulnerability.

Changes:
- keyword_search: capture validated_paths from validate_multiple()
  and use validated_path_strings for tool execution
- performance_check: capture validated_path from validate_exists()
  and use validated_file_path for PerformanceChecker and result output

Addresses PR #49 review feedback.

Co-Authored-By: Claude Opus 4.5 <[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.

1 participant