Skip to content

fix(mcp): recent_runs sees rotated .log.N siblings - #22

Merged
volodchenkov merged 4 commits into
mainfrom
fix/recent-runs-rotated-logs
May 18, 2026
Merged

fix(mcp): recent_runs sees rotated .log.N siblings#22
volodchenkov merged 4 commits into
mainfrom
fix/recent-runs-rotated-logs

Conversation

@volodchenkov

@volodchenkov volodchenkov commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

mcp__plane-conductor__recent_runs globbed only *.log, so once logrotate
truncated the live <ts>-<ws>-<nick>-<short>.log to 0 bytes and parked the
content as <ts>-<ws>-<nick>-<short>.log.1, the MCP returned a wall of
zero-byte stubs and the real history sat invisible. Hit during a coinex audit
this week — production log dir had 350 empty stubs vs 20 non-empty .log.N
files, and I had to fall back to raw ls to find anything.

Two changes in mcp_server.py:

  • _LOG_NAME_RE accepts an optional .N suffix on .log.
  • recent_runs globs *.log*, groups matches by (timestamp, workspace, nickname, issue_short), keeps only the variant with the largest
    size_bytes per group, and sorts on timestamp (was on filename — fine while
    only .log was returned, wrong once .log and .log.1 coexist
    lexicographically).

Added a small targeted test file (tests/test_mcp_server_recent_runs.py) — 5
cases covering rotated-pair dedup, multi-rotation max-wins, distinct-runs
separation, filters + limit, and non-matching siblings (.log.bak, README.md)
correctly ignored.

Test plan

  • New 5 tests green.
  • Full suite green: 205 passed, 7 skipped (e2e gated).
  • Smoke against the real /var/log/plane-conductor/ dir after merge — call recent_runs(workspace='coinex') and confirm the response carries the .log.1 files with non-zero size_bytes instead of the empty .log stubs.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced log file detection to recognize rotated log files in addition to standard log files
    • Improved deduplication of multiple on-disk variants by keeping only the largest version per run
    • Results now correctly sorted by spawn timestamp in descending order with proper limit enforcement
  • Tests

    • Added comprehensive test coverage for rotated log file handling, deduplication logic, run filtering, and result limits

Review Change Stack

Operator-facing symptom: in /var/log/plane-conductor/ logrotate truncates the
live `<ts>-<ws>-<nick>-<short>.log` to 0 bytes and parks the real content as
`<ts>-<ws>-<nick>-<short>.log.1` (and .log.2, ...). recent_runs globbed only
`*.log`, so an audit of «what did our agents actually do last week» returned a
wall of empty stubs and the real history sat invisible. Confirmed against a
production log dir: 350 empty `.log` stubs vs 20 non-empty `.log.N` files —
exactly the spread that made the recent coinex audit impossible to perform
through the MCP tool and forced raw shell ls of the directory.

Two changes:
- `_LOG_NAME_RE` now accepts an optional `.N` suffix on the `.log` extension,
  so the parser doesn't drop rotated siblings.
- recent_runs globs `*.log*`, groups the matches by
  (timestamp, workspace, nickname, issue_short), and keeps only the variant
  with the largest size_bytes per group — the human asking «what did this
  agent run output» wants the file holding the actual stdout, not the
  zero-byte stub. Sort is now stable on timestamp (was on filename, which
  worked while `.log` was the only extension but ordered `.log` ahead of
  `.log.1` lexicographically — wrong if we'd ever returned both).

Added tests/test_mcp_server_recent_runs.py: rotated-pair dedup, multi-rotation
biggest-wins, distinct-runs separation, filters + limit, non-matching siblings
(`.log.bak`, README.md) stay ignored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@volodchenkov has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 57 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 15b1040a-7157-4260-823a-f583aa1e4567

📥 Commits

Reviewing files that changed from the base of the PR and between 3e22e7a and 33f6934.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock, !**/uv.lock
📒 Files selected for processing (3)
  • .pre-commit-config.yaml
  • src/plane_conductor/mcp_server.py
  • tests/test_mcp_server_recent_runs.py

Walkthrough

Updated recent_runs to recognize logrotate-produced filenames (.log.N suffix), deduplicate multiple disk variants by keeping the largest size_bytes, sort by timestamp descending, and limit results. Added comprehensive test suite covering rotated log matching, deduplication, filtering, ordering, and edge cases.

Changes

Logrotate-aware recent_runs deduplication

Layer / File(s) Summary
Log pattern and recent_runs setup
src/plane_conductor/mcp_server.py, tests/test_mcp_server_recent_runs.py
Regex pattern updated to match .log* filenames with rotation suffixes. Function documentation and initial collection mechanism now target *.log*. Test fixtures establish isolated log directory and helper to create test files.
Deduplication and sorting implementation
src/plane_conductor/mcp_server.py, tests/test_mcp_server_recent_runs.py
Core logic converts parsed log variants to candidate records keyed by run identity, deduplicates by selecting largest size_bytes, then sorts by timestamp descending before limiting. Tests verify rotated pairs deduplicate correctly and multiple rotation variants select the largest.
Filtering, sorting, and edge cases
tests/test_mcp_server_recent_runs.py
Tests confirm distinct runs remain separate with descending spawn-time order, filtering by workspace/nickname/issue_prefix works correctly, limit truncates results, and non-log files are ignored.

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: enabling recent_runs to recognize rotated log files with .log.N suffixes, which is the core change across both modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/recent-runs-rotated-logs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Single line-wrap diff flagged by `ruff format --check` in CI on PR #22.
No behavioural change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plane_conductor/mcp_server.py (1)

175-176: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate limit to prevent negative-value behavior.

Negative limit currently yields unexpected subsets instead of an explicit “no results / invalid input” behavior. Guard limit <= 0 before slicing.

Proposed fix
 def recent_runs(
     workspace: str | None = None,
     nickname: str | None = None,
     issue_prefix: str | None = None,
     limit: int = 20,
 ) -> list[dict[str, Any]]:
@@
     ld = _log_dir()
     if not ld.exists():
         return []
+    if limit <= 0:
+        return []
@@
-    return out[:limit]
+    return out[:limit]

Also applies to: 225-225

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plane_conductor/mcp_server.py` around lines 175 - 176, The function that
declares "limit: int = 20) -> list[dict[str, Any]]" needs to explicitly guard
against non-positive limits: check if limit <= 0 at the start of the function
and return an empty list (or raise a ValueError if you prefer) before any
slicing occurs so negative values cannot produce unexpected subsets; apply the
same guard to the other function/block around the second occurrence (the other
place that accepts "limit" near line 225) so both use the same
early-return/validation for the limit parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/plane_conductor/mcp_server.py`:
- Around line 175-176: The function that declares "limit: int = 20) ->
list[dict[str, Any]]" needs to explicitly guard against non-positive limits:
check if limit <= 0 at the start of the function and return an empty list (or
raise a ValueError if you prefer) before any slicing occurs so negative values
cannot produce unexpected subsets; apply the same guard to the other
function/block around the second occurrence (the other place that accepts
"limit" near line 225) so both use the same early-return/validation for the
limit parameter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a80313ec-4df7-4487-93d3-79c9eee6e6ec

📥 Commits

Reviewing files that changed from the base of the PR and between 5d62d94 and 3e22e7a.

📒 Files selected for processing (2)
  • src/plane_conductor/mcp_server.py
  • tests/test_mcp_server_recent_runs.py

Dmitry Volodchenkov and others added 2 commits May 19, 2026 00:55
The file has been generated by uv but never tracked. plane-conductor is a
deployable webhook orchestrator (not a redistributable library), so pinning
the resolved dependency tree gives the same reproducibility win every other
service in the modern Python toolchain expects: CI installs the exact same
versions and hashes as the developer's machine, supply-chain swaps are
caught by hash verification, and a fresh `uv sync` on a contributor's
laptop doesn't drift away from production.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related fixes surfaced by PR #22:

1. CodeRabbit flagged `recent_runs(limit=<=0)` returning a tail-slice
   instead of an explicit empty list — added the early-return guard and
   pinned the behaviour with two tests.

2. The pre-commit mypy hook didn't list fastapi / typer / uvicorn / mcp
   in its additional_dependencies, so any contributor with hooks
   installed got a wall of pre-existing «Untyped decorator», «Cannot
   find module fastapi» errors that don't appear in CI (CI installs
   `.[dev]` which pulls them in). That gap was why the F5 ruff-format
   failure earlier slipped through to CI — pre-commit was effectively
   unusable locally. Hook now matches CI; `pre-commit run --all-files`
   passes clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@volodchenkov
volodchenkov merged commit 5efe9c5 into main May 18, 2026
5 checks passed
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