Skip to content

KonfAI Studio: a truthful chat over konfai-mcp, apps and runtime hardened - #71

Merged
vboussot merged 9 commits into
mainfrom
feat/mcp-restore-app-exec
Jul 29, 2026
Merged

KonfAI Studio: a truthful chat over konfai-mcp, apps and runtime hardened#71
vboussot merged 9 commits into
mainfrom
feat/mcp-restore-app-exec

Conversation

@vboussot

@vboussot vboussot commented Jul 29, 2026

Copy link
Copy Markdown
Member

What this is

Studio was exercised end to end as a new user and as a researcher — synthesis, segmentation, registration, prediction, evaluation, uncertainty, fine-tune, a ~20 min training with live parameter refinement, and a fictional-challenge leaderboard — through the GUI on a real MR↔CT dataset. Every failure found was fixed at its source. Six commits, one per theme:

commit theme
fix(dataset) an entry written during the run is visible across objects and processes (the ImpactSynth auto-mask failure); atomic whole-volume writes; the h5 pooled reader reopens a changed store
fix(evaluator) metrics run on the run's device (1659 s → GPU); PSNR/SSIM rank as maxima
fix(runtime) the console mirror folds progress-bar frames off a terminal (1.6 MB/run of animation in job logs and slurm-*.out, uncapped); CRLF lines are messages, not frames
perf(apps) the HF snapshot is trusted once resolved, not revalidated per job
feat(mcp) app execution restored as normal experiments; experiment state derived from the workspace; the wait_for_job freeze sealed at fd level (dup2 of the whole process tree's stdio off the JSON-RPC channel); ~2300 tokens/turn off the tool index
feat(studio) state-driven buttons, truthful live feed, server-side transcript (multi-browser), viewer follows what is named, run tabs scroll/close, TLS flags, a user's Stop is a stop

Verification

  • Suites on the final tree: core units 1137 passed, 1 skipped; konfai-mcp 193 passed; studio 56 passed.
  • Every regression test in the branch was verified red without its fix; the stdio pair also by mutation (neutralize the fix on a copy → red).
  • The wait_for_job fix is proven on the real stdio transport with a negative control: same noisy training, pre-fix the stream wedges at the fifth poll (the genuine response glued behind 65 bytes of native output), post-fix 26/26 frames parse and the bytes sit in the job log.
  • Two adversarial review rounds over the branch; all confirmed findings are fixed in these commits (CRLF loss, throttled final frame, interleaved bars, cross-session tab leak, empty TLS flag downgrade).

Notes

  • konfai-studio still needs a PyPI trusted publisher under fideus-labs before the next v* tag — the release matrix would otherwise fail on that one package.
  • The committed tool reference (.claude/skills/.../tool-reference.md) is regenerated, not hand-edited.

Summary by CodeRabbit

  • New Features
    • Run/evaluate/uncertainty/pipeline score and fine-tune published apps directly from MCP/Studio flows, with improved app workflow action routing.
    • Studio now uses server-driven workflow next-step suggestions, volume comparison pairing, richer GPU/CPU metrics, and improved run tab behavior.
  • Bug Fixes
    • Safer dataset concurrency (including HDF5), improved HDF5/SimpleITK writes, and more reliable subprocess/job stdio isolation.
    • Corrected metric direction normalization (including PSNR/SSIM).
    • Updated “weightless” readiness handling to avoid unnecessary checkpoint blocking.
  • Documentation
    • Refreshed MCP tool reference and clarified published-app decision flows, plus HTTPS self-managed TLS setup.

vboussot added 6 commits July 29, 2026 16:59
is_dataset_exist answered from a directory listing memoised at first use, so
a group produced during the run through another Dataset object -- or, with
loader workers, another process -- stayed invisible: the auto-mask path of a
synthesis bundle read the mask its own run had just written and found
nothing, in 4 runs out of 10. Membership now asks the disk for the entry
itself, and a whole-volume write publishes by rename (a dotted staging name,
so extension-based writer selection survives).

On h5 the pooled read handle kept serving the view it opened on; reopening
alone would not help, since HDF5 shares a file's metadata state across the
handles one process holds. The pool now closes a handle whose store changed
underneath it, and retries the reopen that lands while the writer is still
mid-transaction. The transforms doc says what a Save-written group promises
on each backend.

Measured: membership 568 us -> 6.3 us (directory) / 1.8 us (h5); the stat
probe costs 0.7% of a pooled read.
The evaluator placed nothing on the device it was given, so every metric ran
on CPU -- 1659 s for an evaluation the GPU does in a fraction of that. And
PSNR and SSIM carried no maximize flag, so any consumer ranking runs by them
put the worst first.
The mirror replayed every raw redraw frame of a progress bar to what it
believes is the console. Inside an MCP job or under nohup/SLURM that target
is a file, where nothing overwrites: the animation alone reached 1.6 MB per
smoke run (6369 frames), uncapped. Off a terminal the mirror now sends the
folded line, at most one frame per 2 s window per bar; withheld frames land
before the next message and at exit, so a bar's final state is never lost --
including with interleaved train/validation bars. A CRLF-terminated line is
a message, not an animation frame: classified as one it vanished from the
mirror and the log file both. On a real console nothing changes.
Resolving an app re-checked every file of the HuggingFace snapshot against
the hub before each job; the check is network-bound and the answer never
changes within a session. The snapshot is trusted once resolved.
Apps run as normal experiments again: import_app brings a bundle into the
session and run_prediction / run_resume / run_evaluation drive it, with the
app-execution tools and their tests restored. The experiment's state is
derived from the workspace on disk -- the same answer for every client --
and each stage names the step it is waiting on.

The wait_for_job freeze is sealed at fd level. redirect_stdout is
Python-level: fds 1/2 stayed on the inherited descriptors -- the JSON-RPC
stream itself -- so anything writing natively (KonfAI's console mirror in a
DDP grandchild, pip during app resolution) destroyed the response it
followed. Jobs dup2 their log onto fds 1/2 and devnull onto fd 0 before the
target runs; the isolated API child does the same into a sink whose tail is
attached to any failure, including a dup2 that itself fails. Verified on
the real transport: pre-fix the stream wedges at the fifth poll, post-fix
26/26 frames parse and the bytes sit in the job log.

Payloads say what an agent needs: inspect_dataset is one object per group;
a leaderboard metric ranks one way for every run; a weightless model runs
without a checkpoint; a job ending on SIGTERM with cancellation requested
is 'killed' and diagnosed as a deliberate stop, never a failure to
investigate. The fixed index cost drops ~2300 tokens per turn ('Inputs:'
sentences restated the Field descriptions; duplicated wordings unified);
the committed tool reference is regenerated.
The chat is driven by the experiment state the MCP server derives, and every
turn ends in buttons that are the reply's real next moves -- its answers
when it asks, the corrections when a run fails, never a padded count. The
live feed tells the truth: a run tab opens at launch, curves and progress
stream in, Stop resolves, and a stopped run is acknowledged as the user's
deliberate act. The viewer follows what the assistant names, two volumes
side by side with linked crosshairs.

The transcript is served by the server: it records every turn it streams
(interrupted ones included), and a browser whose localStorage has fewer
turns adopts the copy -- an experiment driven from one machine no longer
looks empty from another. The rail discovers experiments created elsewhere
through the status poll it already pays for.

Run tabs scroll instead of clipping, carry their status, and close without
deleting anything; the mic says why it cannot work on an insecure origin;
--ssl-certfile/--ssl-keyfile serve TLS directly (REMOTE.md shows the
self-signed one-liner), with an empty flag refused rather than silently
downgraded to plain http.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24011bcd-6601-42f2-ab78-7455e5ea8b1d

📥 Commits

Reviewing files that changed from the base of the PR and between 2a84614 and 474e520.

📒 Files selected for processing (2)
  • konfai-mcp/konfai_mcp/runner.py
  • konfai-mcp/tests/test_mcp_server_apps.py

📝 Walkthrough

Walkthrough

The PR adds app execution and experiment-state workflows, changes dataset and runtime I/O behavior, and integrates server-derived workflow state into Studio chat, live feeds, telemetry, and workspace controls. It also adds regression tests and refreshed documentation.

Changes

MCP app workflows and experiment state

Layer / File(s) Summary
Published app execution and routing
konfai-mcp/konfai_mcp/*, konfai-apps/..., docs/...
Published apps gain dedicated execution and fine-tuning paths with parameter overrides, trust checks, job tracking, and updated routing guidance.
Experiment state and dataset contracts
konfai-mcp/konfai_mcp/{experiment_state,dataset_inspection,metrics_service}.py
Workspace stages, diagnoses, dataset layouts, incomplete groups, and metric directions are derived from updated filesystem and payload contracts.
MCP validation coverage
konfai-mcp/tests/*
Tests cover app preparation, job refinement, experiment state, dataset responses, tool registration, and subprocess behavior.

Core runtime and dataset I/O

Layer / File(s) Summary
Evaluation and progress logging
konfai/evaluator.py, konfai/metric/measure.py, konfai/utils/runtime.py
Metric tensors use a configured device, PSNR and SSIM are higher-is-better, and progress mirroring handles redraw frames safely.
Dataset and subprocess I/O
konfai/utils/dataset.py, konfai-mcp/konfai_mcp/runner.py, tests/unit/*
HDF5 readers detect stale files, image writes publish atomically, membership checks probe storage directly, and child stdio is isolated.

Studio workflow and streaming interface

Layer / File(s) Summary
Server workflow and persistence
studio/konfai_studio/{server,workflow,transcript,registry,paths}.py
Chat turns derive state and moves from workspace facts, transcripts and move state persist per session, and session paths are centralized.
Agent and live-feed streaming
studio/konfai_studio/{agent,jobs}.py
Streaming supports next-move markers, volume comparisons, interruption, uncapped tool payloads, keep-alives, and stable run announcements.
Frontend workspace behavior
studio/frontend/src/*
Studio displays workflow prompts, telemetry, comparison volumes, session-scoped job tabs, reconnect watchdogs, and secure-origin microphone state.
Studio validation and TLS
studio/tests/*, studio/docs/REMOTE.md
End-to-end chat, live-feed, workflow, cache-header, volume-pairing, and TLS behavior is covered.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

A rabbit watched the workflows hop,
Apps ran quickly, datasets popped.
Logs stayed quiet, streams stayed bright,
Tabs compared volumes right.
Fresh carrots for each run—
KonfAI’s burrow work is done!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description does not follow the required template and is missing the prescribed sections and checklist items. Rewrite it using the repo template: Description, Related issues, Type of change, How tested, Checklist, and Breaking changes & migration.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and related to the main changes, especially Studio chat and runtime hardening.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mcp-restore-app-exec

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

@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.

Actionable comments posted: 16

Caution

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

⚠️ Outside diff range comments (3)
konfai-mcp/konfai_mcp/dataset_inspection.py (1)

314-316: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale is_input_meaning reference.

The removed payload field is still named in the adjacent comment, so the guidance points at a key that no longer exists. Point the comment to design_config_strategy or group-role clarification instead.

🤖 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 `@konfai-mcp/konfai_mcp/dataset_inspection.py` around lines 314 - 316, Update
the adjacent comment near the payload containing dataset_entry and
suggested_groups_src to remove the stale is_input_meaning reference, and instead
direct guidance toward design_config_strategy or clarifying group roles.
konfai-mcp/konfai_mcp/server.py (1)

1035-1056: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

inspect_dataset's advertised outputs still describe the removed sibling statistics map.

Statistics now live under payload["groups"][group], and the top-level statistics / statistics_groups / statistics_sample_limit keys are gone, but konfai-mcp/konfai_mcp/guide.py Line 49 still says "Outputs: groups, statistics, dataset_entry hints, warnings, and next_actions." An agent reading the tool index will look for a key that no longer exists.

📝 Suggested description fix (konfai-mcp/konfai_mcp/guide.py)
-        "Outputs: groups, statistics, dataset_entry hints, warnings, and next_actions. "
+        "Outputs: groups (each carrying its own sampled statistics), dataset_entry hints, warnings, "
+        "statistics_missing_groups/reasons, and next_actions. "
🤖 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 `@konfai-mcp/konfai_mcp/server.py` around lines 1035 - 1056, Update the
inspect_dataset output description in guide.py to remove the obsolete top-level
“statistics” output and describe statistics as being included within “groups”.
Keep the remaining advertised outputs, including dataset_entry hints, warnings,
and next_actions, unchanged.
konfai-mcp/konfai_mcp/runner.py (1)

101-161: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Timeout path leaks the temp dir and drops the child's captured output.

The TimeoutError at Lines 116-119 returns before _drain(output) at Line 142, so Path(tempfile.mkdtemp(...)) is never removed (one leaked dir + log per timed-out job), and the tail — the only evidence of what a hung child was doing — is discarded exactly where it is most useful.

🛡️ Proposed fix: drain in a finally / include the tail in the timeout
-    process = context.Process(target=entry, args=(queue, target, kwargs, str(output)), daemon=True)
-    process.start()
-    result: dict[str, Any] | None = None
-    while result is None:
-        try:
-            result = queue.get(timeout=0.5)
-        except Empty:
-            if deadline is not None and time.monotonic() > deadline and process.is_alive():
-                process.terminate()
-                process.join(5)
-                if process.is_alive():
-                    process.kill()
-                    process.join(5)
-                raise TimeoutError(
-                    f"Isolated subprocess '{target}' exceeded {timeout_s:.0f}s and was terminated. "
-                    "Raise KONFAI_MCP_SUBPROCESS_TIMEOUT if this is a large model, or simplify the config."
-                ) from None
+    process = context.Process(target=entry, args=(queue, target, kwargs, str(output)), daemon=True)
+    process.start()
+    result: dict[str, Any] | None = None
+    while result is None:
+        try:
+            result = queue.get(timeout=0.5)
+        except Empty:
+            if deadline is not None and time.monotonic() > deadline and process.is_alive():
+                process.terminate()
+                process.join(5)
+                if process.is_alive():
+                    process.kill()
+                    process.join(5)
+                printed = _drain(output)
+                message = (
+                    f"Isolated subprocess '{target}' exceeded {timeout_s:.0f}s and was terminated. "
+                    "Raise KONFAI_MCP_SUBPROCESS_TIMEOUT if this is a large model, or simplify the config."
+                )
+                raise TimeoutError(
+                    f"{message}\n\nSubprocess output (tail):\n{printed}" if printed else message
+                ) from None
🤖 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 `@konfai-mcp/konfai_mcp/runner.py` around lines 101 - 161, Update the
subprocess timeout handling around the process loop and _drain so timed-out jobs
always drain and remove the captured output directory before raising. Preserve
the existing termination escalation, capture the returned output tail, and
include it in the TimeoutError when present so diagnostics are retained.
🧹 Nitpick comments (9)
konfai/evaluator.py (1)

444-448: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

non_blocking on a CPU→GPU copy of unpinned memory is a no-op; and D2D copies lose it.

tensor.device.type == "cpu" gates async copies to the H2D direction, but the evaluation tensors come straight from the DataLoader and are only asynchronous if pinned. Harmless, just note it buys nothing unless the loader pins memory.

🤖 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 `@konfai/evaluator.py` around lines 444 - 448, Update the _on helper’s
tensor.to call so non_blocking is enabled only when the source CPU tensor is
pinned, preserving synchronous behavior for unpinned DataLoader tensors and
device-to-device copies.
konfai/utils/runtime.py (2)

469-489: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

_mirror_pending is mutated from every thread that writes to stdout.

MinimalLog replaces the process-wide sys.stdout, so bar frames can arrive from more than one thread (tqdm in a background validation/prediction thread). _mirror_take_pending iterates _mirror_pending and then clears it, so a concurrent write can have its frame dropped or hit a mid-iteration mutation. A small lock around the pending dict (or dict.copy()+swap) would make this deterministic.

🤖 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 `@konfai/utils/runtime.py` around lines 469 - 489, The _mirror_pending
dictionary in MinimalLog is accessed concurrently by stdout-writing threads,
allowing _mirror_take_pending and write to race during iteration and clearing.
Add a dedicated lock initialized alongside _mirror_pending, and use it to
protect every pending-dictionary read, update, iteration, and clear in the
related mirror methods, preserving the existing throttling and final-frame
behavior.

519-534: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The throttle timestamp is global, so interleaved bars share one window.

_mirror_last_redraw is a single instant for all bars: with a train and a validation bar redrawing together, only one bar's frame is emitted per 2 s window and the other's live state stays pending until the window rolls (or __exit__). A tailed log then shows one bar advancing and the other frozen. Keying the last-emit time per _bar_key restores a truthful live feed for both at the same cost.

Also worth noting: the mirror is folded, but Log.write (Line 606-610) still appends _buffered_line for every frame to log_<rank>.txt, so the on-disk run log keeps the per-frame growth this change set out to remove. Please confirm that is intended.

♻️ Proposed per-bar throttle
-        self._mirror_last_redraw = 0.0
+        self._mirror_last_redraw: dict[str, float] = {}
             if redraw:
                 now = time.monotonic()
-                if now - self._mirror_last_redraw < self._MIRROR_REDRAW_EVERY:
-                    self._mirror_pending[_bar_key(self._buffered_line)] = self._buffered_line
+                key = _bar_key(self._buffered_line)
+                if now - self._mirror_last_redraw.get(key, 0.0) < self._MIRROR_REDRAW_EVERY:
+                    self._mirror_pending[key] = self._buffered_line
                     return
-                self._mirror_last_redraw = now
-                held = self._mirror_take_pending(exclude=_bar_key(self._buffered_line))
+                self._mirror_last_redraw[key] = now
+                held = self._mirror_take_pending(exclude=key)
                 msg = f"{held}{self._buffered_line}\n"
🤖 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 `@konfai/utils/runtime.py` around lines 519 - 534, Update _mirror so redraw
throttling uses a per-bar timestamp keyed by _bar_key(self._buffered_line),
allowing interleaved bars to emit independently while preserving pending-frame
handling. Also update Log.write so log_<rank>.txt does not append every redraw
frame through _buffered_line; ensure the on-disk log follows the intended
folded, throttled output behavior.
konfai/utils/dataset.py (1)

160-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Putting an evicted handle back bypasses the LRU bound.

_close_idle re-inserts under setdefault without re-checking _MAX, so a file whose lock is repeatedly busy keeps the pool above the bound (and the reinserted entry lands at the tail, making it the last eviction candidate). Not harmful at _MAX = 8, but the invariant the comment relies on ("LRU-bounded") no longer holds strictly.

🤖 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 `@konfai/utils/dataset.py` around lines 160 - 173, The _close_idle method must
preserve the pool’s _MAX LRU bound when reinserting a handle whose file lock is
busy. Re-check capacity while holding _guard, retain the handle only when space
is available, and otherwise evict/close the appropriate existing entry so
reinsertion cannot exceed the limit or undermine LRU ordering.
konfai-mcp/README.md (1)

231-232: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

set_parameters is a tool argument, not a tool.

Listing it beside list_app_parameters reads as a second tool name; the surrounding bullets are all tool names. Elsewhere in this doc/registry it is the per-run parameter of run_app_* / export_app.

📝 Suggested wording
-   - `list_app_parameters` / `set_parameters` — read tunable parameters (with
-     their constraints) and override them per run
+   - `list_app_parameters` — read tunable parameters (with their constraints);
+     override them per run via the `set_parameters` argument of `run_app_*`
🤖 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 `@konfai-mcp/README.md` around lines 231 - 232, Update the README entry
containing `list_app_parameters` / `set_parameters` so `set_parameters` is
described as a per-run argument for `run_app_*` and `export_app`, not as a
separate tool name; keep `list_app_parameters` identified as the tool.
konfai-mcp/konfai_mcp/runner.py (1)

361-373: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider an explicit action allowlist before the getattr dispatch.

action is trusted to come from _prepare_action (only evaluate / uncertainty / pipeline), so nothing is exploitable today, but this is a public runner entrypoint invoked with job-persisted kwargs — a recovered/hand-edited job spec would call an arbitrary KonfAIApp attribute. A one-line membership check makes the contract local and turns a mismatch into a clear error instead of a TypeError deep in the child.

♻️ Proposed guard
     with _runtime_context(cwd=Path(cwd).resolve() if cwd is not None else None):
         _ensure_local_imports()
+        if action not in ("evaluate", "uncertainty", "pipeline"):
+            raise ValueError(f"Unsupported app action: {action!r}")
         from konfai_apps.app import KonfAIApp
🤖 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 `@konfai-mcp/konfai_mcp/runner.py` around lines 361 - 373, Update the public
runner dispatch around getattr(KonfAIApp(...), action) to validate action
against the explicit supported allowlist: evaluate, uncertainty, and pipeline.
Reject any other value with a clear error before attribute lookup or invocation,
while preserving the existing argument construction and dispatch behavior for
allowed actions.
studio/konfai_studio/agent.py (1)

618-634: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

An always-empty mcpServers list costs the full 15 s on every session open.

if servers and all(...) never returns early when the SDK build reports an empty list (or omits the key), so __aenter__ blocks for _MCP_READY_TIMEOUT before the first turn instead of the ~2 s handshake this is meant to cover. Treating "no servers reported" as "nothing to wait for" keeps the guard for the real case and drops the pathological one.

♻️ Suggested tweak
-            servers = (status or {}).get("mcpServers") or []
-            if servers and all(s.get("status") != "pending" for s in servers):
-                return
+            servers = (status or {}).get("mcpServers")
+            if not servers:  # nothing reported: this build has no status to wait on
+                return
+            if all(s.get("status") != "pending" for s in servers):
+                return
🤖 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 `@studio/konfai_studio/agent.py` around lines 618 - 634, Update
_await_mcp_tools so an empty or missing mcpServers collection returns
immediately as “nothing to wait for,” while retaining the existing wait until
all reported servers are no longer pending.
studio/frontend/src/RightPanel.tsx (1)

1453-1463: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The tab-close affordance is mouse-only.

A <span> with onClick is not focusable and has no keyboard activation, so a keyboard user cannot close a run tab (nesting a real <button> inside the tab button is invalid, so the usual fix is role="button" tabIndex={0} plus an onKeyDown for Enter/Space, or lifting the ✕ out of the tab button). The "+N closed" chip is a button, so this is the only unreachable control here.

🤖 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 `@studio/frontend/src/RightPanel.tsx` around lines 1453 - 1463, The tab-close
span in the tab rendering should be keyboard-accessible. Update the rtab-close
element with button semantics, make it focusable, and handle Enter and Space in
onKeyDown by invoking the same hide/show behavior as onClick while preventing
unintended tab activation; preserve event propagation prevention.
studio/frontend/src/Chat.tsx (1)

516-524: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Two moves sharing a label collide on key.

The LLM writes these labels freely and _NextMoves.moves() in studio/konfai_studio/agent.py does not dedupe them (unlike workflow.moves), so a repeated 2-4 word label yields duplicate React keys and one chip can drop or mis-update. key={\${c.label}-${i}`}(or deduping inmoves()`) settles it.

🤖 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 `@studio/frontend/src/Chat.tsx` around lines 516 - 524, Update the `shown.map`
rendering in `Chat.tsx` to give each prompt chip a unique React key even when
multiple moves share the same `label`; use the map index together with `c.label`
(or deduplicate moves in `_NextMoves.moves()`). Preserve the existing prompt
labels, titles, and click behavior.
🤖 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.

Inline comments:
In `@docs/source/usage/mcp.md`:
- Around line 45-49: The published-app guidance around run_app_infer and
run_app_pipeline and the corresponding tool table must distinguish local/Hugging
Face apps from remote host:port:name servers. State that remote apps should be
run directly with konfai-apps, while retaining the MCP-tool workflow for
supported local and Hugging Face apps.

In `@konfai-mcp/konfai_mcp/dataset_inspection.py`:
- Around line 57-58: Update the ignored-files assignment in the dataset
inspection layout-building logic to slice summary["ignored_files"] using a
dedicated cap before storing it in entry["ignored_files"]. Preserve the existing
conditional and avoid modifying entries that already define ignored_files.

In `@konfai-mcp/konfai_mcp/experiment_state.py`:
- Around line 104-116: Update the validation or registry-drift checks to
explicitly inspect konfai_mcp.experiment_state.STAGE_ACTIONS, verifying every
listed action is registered as a tool and failing with a clear diagnostic for
unknown names. Reuse the existing job-payload guard’s registry source and
validation behavior, while preserving the current stage-to-action defaults.

In `@konfai-mcp/konfai_mcp/metrics_service.py`:
- Around line 303-315: Extract the metric-direction resolution currently used in
compare_runs into a shared helper, including direction_source determination and
stale-direction warnings. Update compare_runs_payload to use this normalized
current direction for both runs instead of choosing declared_a.get(name) or
declared_b.get(name), so swapping run_a and run_b cannot change the winner.
Preserve explicit direction overrides and the existing leaderboard behavior.

In `@konfai-mcp/konfai_mcp/server_jobs.py`:
- Around line 40-60: Update _detach_inherited_stdio so opening devnull and
detaching file descriptor 0 use a separate try/except from the stdout/stderr
dup2 calls. Ensure any stdin-related OSError is reported without returning
early, then always attempt os.dup2(handle.fileno(), 1) and
os.dup2(handle.fileno(), 2), while preserving the existing error reporting
behavior.

In `@konfai-mcp/konfai_mcp/server_support.py`:
- Around line 499-503: Update the “Uncertainty” guidance in the authoring_checks
configuration to clarify that run_app_infer(uncertainty=True) retains
multi-channel inference stacks, which are then consumed by run_app_uncertainty
to produce uncertainty maps; do not describe inference completion itself as the
uncertainty result.

In `@konfai-mcp/tests/test_mcp_server_apps.py`:
- Around line 252-419: Add an end-to-end async fastmcp.Client test covering the
new app-execution tools, using a stubbed launch path to avoid starting a real
job. Invoke run_app_infer through mcp_server.mcp and assert its validated
parameters, completed output and metrics, plus the returned output,
set_parameters, and next_actions payload fields. Extend coverage as needed for
run_app_evaluate, run_app_uncertainty, run_app_pipeline, and fine_tune_app so
all five client-exposed tools are exercised.

In `@konfai/utils/dataset.py`:
- Around line 1232-1239: Update the staging-name handling in the image write
path around sitk.WriteImage and os.replace so temporary files retain a suffix
recognized by the existing _resolve_data_path and file_to_data .tmp checks,
while preserving the format extension required by SimpleITK. Also update
Dataset.get_group to ignore leading-dot staging names so in-progress or orphaned
files cannot produce an empty group name.
- Around line 1906-1927: Update is_dataset_exist in the directory-backed branch
so it returns True only when the HDF5 leaf for the requested name exists under
entry_group, rather than relying solely on file.is_exist(entry_group). Preserve
the existing directory probing and return False behavior when the requested
child is absent.

In `@studio/frontend/src/App.tsx`:
- Around line 351-355: Update the setGpuVramHist callback to skip appending a
VRAM sample when the GPU has no valid memory usage or total value, matching the
existing gpuHist guard for null utilization. Only append pct(g.used_gb,
g.total_gb) for valid reports, while preserving existing history entries and cap
behavior.

In `@studio/frontend/src/Chat.tsx`:
- Around line 190-210: Update the history-loading useEffect and its setMessages
merge logic to detect whether a turn is currently in flight, and skip replacing
the local message list while that turn is active. Preserve the existing
stale-session guard and server-history merge behavior when no send/streaming
operation is running, ensuring the assistant placeholder used by send and
patchAssistant remains available.

In `@studio/konfai_studio/agent.py`:
- Around line 643-657: Reset _interrupted at the start of every new turn, before
processing that turn’s result or error state, so a late interrupt cannot
suppress a subsequent turn’s genuine failure. Update the turn-start logic
associated with interrupt handling and preserve the existing
reset-on-interrupt-exception behavior.

In `@studio/konfai_studio/jobs.py`:
- Around line 276-290: Update the duplicate-run naming expression in the list
comprehension around _discover_run_logs so the disambiguating suffix is derived
from the actual `-<hash>` component of the output directory, regardless of
whether the log is nested under Statistics, Predictions, Evaluations, or
Uncertainties. Preserve the existing run name for non-duplicates and continue
using the suffix only when `(run, kind)` is duplicated and `base` is present.

In `@studio/konfai_studio/server.py`:
- Around line 227-235: Guard the post-turn state and next-prompts generation
within the stream’s existing protected flow so failures from `_state` or
`_turn_moves` do not escape `gen()` and prevent the final events; preserve the
intended state/move completion behavior. In the `record_turn` call, offload the
synchronous file I/O to a worker thread using the same `asyncio.to_thread`
pattern as `_system_snapshot`, awaiting it before emitting the post-turn events.

In `@studio/tests/test_live_feed.py`:
- Around line 45-69: Update the collect function in feed_events to replace
asyncio.timeout with a timeout mechanism compatible with Python 3.10, while
preserving the existing two-second limit, event collection, cancellation
handling, and stream cleanup behavior.

In `@tests/unit/test_dataset.py`:
- Around line 557-595: Update
test_an_evicted_h5_handle_goes_back_with_the_view_it_had so the child process is
always cleaned up after join(120). Wrap the join and exit-code assertion in a
try/finally block, and call child.terminate() in finally only when
child.is_alive() remains true.

---

Outside diff comments:
In `@konfai-mcp/konfai_mcp/dataset_inspection.py`:
- Around line 314-316: Update the adjacent comment near the payload containing
dataset_entry and suggested_groups_src to remove the stale is_input_meaning
reference, and instead direct guidance toward design_config_strategy or
clarifying group roles.

In `@konfai-mcp/konfai_mcp/runner.py`:
- Around line 101-161: Update the subprocess timeout handling around the process
loop and _drain so timed-out jobs always drain and remove the captured output
directory before raising. Preserve the existing termination escalation, capture
the returned output tail, and include it in the TimeoutError when present so
diagnostics are retained.

In `@konfai-mcp/konfai_mcp/server.py`:
- Around line 1035-1056: Update the inspect_dataset output description in
guide.py to remove the obsolete top-level “statistics” output and describe
statistics as being included within “groups”. Keep the remaining advertised
outputs, including dataset_entry hints, warnings, and next_actions, unchanged.

---

Nitpick comments:
In `@konfai-mcp/konfai_mcp/runner.py`:
- Around line 361-373: Update the public runner dispatch around
getattr(KonfAIApp(...), action) to validate action against the explicit
supported allowlist: evaluate, uncertainty, and pipeline. Reject any other value
with a clear error before attribute lookup or invocation, while preserving the
existing argument construction and dispatch behavior for allowed actions.

In `@konfai-mcp/README.md`:
- Around line 231-232: Update the README entry containing `list_app_parameters`
/ `set_parameters` so `set_parameters` is described as a per-run argument for
`run_app_*` and `export_app`, not as a separate tool name; keep
`list_app_parameters` identified as the tool.

In `@konfai/evaluator.py`:
- Around line 444-448: Update the _on helper’s tensor.to call so non_blocking is
enabled only when the source CPU tensor is pinned, preserving synchronous
behavior for unpinned DataLoader tensors and device-to-device copies.

In `@konfai/utils/dataset.py`:
- Around line 160-173: The _close_idle method must preserve the pool’s _MAX LRU
bound when reinserting a handle whose file lock is busy. Re-check capacity while
holding _guard, retain the handle only when space is available, and otherwise
evict/close the appropriate existing entry so reinsertion cannot exceed the
limit or undermine LRU ordering.

In `@konfai/utils/runtime.py`:
- Around line 469-489: The _mirror_pending dictionary in MinimalLog is accessed
concurrently by stdout-writing threads, allowing _mirror_take_pending and write
to race during iteration and clearing. Add a dedicated lock initialized
alongside _mirror_pending, and use it to protect every pending-dictionary read,
update, iteration, and clear in the related mirror methods, preserving the
existing throttling and final-frame behavior.
- Around line 519-534: Update _mirror so redraw throttling uses a per-bar
timestamp keyed by _bar_key(self._buffered_line), allowing interleaved bars to
emit independently while preserving pending-frame handling. Also update
Log.write so log_<rank>.txt does not append every redraw frame through
_buffered_line; ensure the on-disk log follows the intended folded, throttled
output behavior.

In `@studio/frontend/src/Chat.tsx`:
- Around line 516-524: Update the `shown.map` rendering in `Chat.tsx` to give
each prompt chip a unique React key even when multiple moves share the same
`label`; use the map index together with `c.label` (or deduplicate moves in
`_NextMoves.moves()`). Preserve the existing prompt labels, titles, and click
behavior.

In `@studio/frontend/src/RightPanel.tsx`:
- Around line 1453-1463: The tab-close span in the tab rendering should be
keyboard-accessible. Update the rtab-close element with button semantics, make
it focusable, and handle Enter and Space in onKeyDown by invoking the same
hide/show behavior as onClick while preventing unintended tab activation;
preserve event propagation prevention.

In `@studio/konfai_studio/agent.py`:
- Around line 618-634: Update _await_mcp_tools so an empty or missing mcpServers
collection returns immediately as “nothing to wait for,” while retaining the
existing wait until all reported servers are no longer pending.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d132351-7991-485b-a2ea-155cfc84e508

📥 Commits

Reviewing files that changed from the base of the PR and between 04c3269 and 3380b51.

📒 Files selected for processing (57)
  • .claude/skills/konfai-experiments/SKILL.md
  • .claude/skills/konfai-experiments/references/tool-reference.md
  • docs/source/reference/components/transforms.md
  • docs/source/usage/mcp.md
  • konfai-apps/konfai_apps/app.py
  • konfai-mcp/README.md
  • konfai-mcp/konfai_mcp/capabilities.py
  • konfai-mcp/konfai_mcp/dataset_inspection.py
  • konfai-mcp/konfai_mcp/experiment_state.py
  • konfai-mcp/konfai_mcp/guide.py
  • konfai-mcp/konfai_mcp/metrics_service.py
  • konfai-mcp/konfai_mcp/runner.py
  • konfai-mcp/konfai_mcp/server.py
  • konfai-mcp/konfai_mcp/server_apps.py
  • konfai-mcp/konfai_mcp/server_experiments.py
  • konfai-mcp/konfai_mcp/server_jobs.py
  • konfai-mcp/konfai_mcp/server_support.py
  • konfai-mcp/konfai_mcp/workflows.py
  • konfai-mcp/tests/mcp_test_helpers.py
  • konfai-mcp/tests/test_experiment_state.py
  • konfai-mcp/tests/test_mcp_server_apps.py
  • konfai-mcp/tests/test_mcp_server_dataset_tools.py
  • konfai-mcp/tests/test_mcp_server_fixes.py
  • konfai-mcp/tests/test_mcp_server_jobs.py
  • konfai-mcp/tests/test_mcp_server_lot5.py
  • konfai-mcp/tests/test_mcp_server_refine_loop.py
  • konfai-mcp/tests/test_mcp_server_tool_index.py
  • konfai-mcp/tests/test_review_mcp_apps_misc.py
  • konfai-mcp/tests/test_workflow_registry.py
  • konfai/evaluator.py
  • konfai/metric/measure.py
  • konfai/utils/dataset.py
  • konfai/utils/runtime.py
  • studio/docs/REMOTE.md
  • studio/frontend/.gitignore
  • studio/frontend/src/App.tsx
  • studio/frontend/src/Chat.tsx
  • studio/frontend/src/RightPanel.tsx
  • studio/frontend/src/sessionState.ts
  • studio/frontend/src/styles.css
  • studio/frontend/src/useJobStream.ts
  • studio/konfai_studio/agent.py
  • studio/konfai_studio/cli.py
  • studio/konfai_studio/jobs.py
  • studio/konfai_studio/paths.py
  • studio/konfai_studio/registry.py
  • studio/konfai_studio/server.py
  • studio/konfai_studio/tensorboard.py
  • studio/konfai_studio/transcript.py
  • studio/konfai_studio/workflow.py
  • studio/tests/test_chat_flow.py
  • studio/tests/test_live_feed.py
  • studio/tests/test_smoke.py
  • studio/tests/test_workflow.py
  • tests/unit/test_dataset.py
  • tests/unit/test_dataset_streaming.py
  • tests/unit/test_runtime.py

Comment thread docs/source/usage/mcp.md
Comment thread konfai-mcp/konfai_mcp/dataset_inspection.py Outdated
Comment thread konfai-mcp/konfai_mcp/experiment_state.py
Comment thread konfai-mcp/konfai_mcp/metrics_service.py
Comment thread konfai-mcp/konfai_mcp/server_jobs.py
Comment thread studio/konfai_studio/agent.py
Comment thread studio/konfai_studio/jobs.py
Comment thread studio/konfai_studio/server.py Outdated
Comment thread studio/tests/test_live_feed.py
Comment on lines +557 to +595
def test_an_evicted_h5_handle_goes_back_with_the_view_it_had(tmp_path: Path) -> None:
"""A handle evicted while its file lock is busy returns to the pool instead of being closed. Re-stamping
it on the way back would hand it the store as it is now and launder a stale view into a fresh-looking
one, so the write that arrived meanwhile would stay invisible for the rest of the process."""
pytest.importorskip("h5py")
from konfai.utils.dataset import _h5_read_pool

root = str(tmp_path / "ds") + "/"
Path(root).mkdir()
dataset = Dataset(root, "h5")
dataset.write("MASK", "P000", np.ones((4, 4, 4), dtype=np.uint8), Attribute())
dataset.is_dataset_exist("MASK", "P000") # pool a handle on the store
store = dataset.filename + ".h5"
pooled = _h5_read_pool._handles.pop(store)

child = multiprocessing.get_context("spawn").Process(target=_write_mask_in_child_h5, args=(root, "P001"))
child.start()
child.join(120)
assert child.exitcode == 0, "the writer process failed; the assertions below would prove nothing"

# The file lock is reentrant, so only another thread can make it look busy to the evicting one.
held, release = threading.Event(), threading.Event()

def hold_the_file_lock() -> None:
with _get_h5_file_lock(store):
held.set()
release.wait(120)

holder = threading.Thread(target=hold_the_file_lock)
holder.start()
try:
assert held.wait(120)
_h5_read_pool._close_idle(store, pooled)
assert _h5_read_pool._handles[store].opened_on == pooled.opened_on, "the view it had, not the store now"
finally:
release.set()
holder.join(120)

assert dataset.is_dataset_exist("MASK", "P001")

@coderabbitai coderabbitai Bot Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '1,40p' tests/unit/test_dataset.py
rg -n '_get_h5_file_lock|^import threading|threading\.' tests/unit/test_dataset.py

Repository: fideus-labs/KonfAI

Length of output: 2175


🏁 Script executed:

python3 - <<'PY'
import multiprocessing as mp
import time

def sleeper():
    time.sleep(2)

p = mp.get_context("spawn").Process(target=sleeper)
p.start()
p.join(0.2)
print("alive_after_timeout=", p.is_alive())
print("exitcode_after_timeout=", p.exitcode)
# leave it running? terminate to avoid orphaning in this probe
p.terminate()
p.join(2)
print("alive_after_terminate=", p.is_alive())
print("exitcode_after_terminate=", p.exitcode)
PY

Repository: fideus-labs/KonfAI

Length of output: 1156


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
import multiprocessing as mp
import time

def sleeper():
    time.sleep(2)

p = mp.get_context("spawn").Process(target=sleeper)
p.start()
p.join(0.2)
print("alive_after_timeout=", p.is_alive())
print("exitcode_after_timeout=", p.exitcode)
p.terminate()
p.join(2)
print("alive_after_terminate=", p.is_alive())
print("exitcode_after_terminate=", p.exitcode)
PY

Repository: fideus-labs/KonfAI

Length of output: 1156


🏁 Script executed:

cat >/tmp/mp_timeout_probe.py <<'PY'
import multiprocessing as mp
import time

def sleeper():
    time.sleep(2)

if __name__ == "__main__":
    p = mp.get_context("spawn").Process(target=sleeper)
    p.start()
    p.join(0.2)
    print("alive_after_timeout=", p.is_alive())
    print("exitcode_after_timeout=", p.exitcode)
    if p.is_alive():
        p.terminate()
    p.join(2)
    print("alive_after_cleanup=", p.is_alive())
    print("exitcode_after_cleanup=", p.exitcode)
PY
python3 /tmp/mp_timeout_probe.py
rm -f /tmp/mp_timeout_probe.py

Repository: fideus-labs/KonfAI

Length of output: 266


Terminate the child on timeout
Wrap the join(120) path in finally and call child.terminate() if it is still alive; otherwise a slow CI run can leave the spawned process running.

🤖 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 `@tests/unit/test_dataset.py` around lines 557 - 595, Update
test_an_evicted_h5_handle_goes_back_with_the_view_it_had so the child process is
always cleaned up after join(120). Wrap the join and exit-code assertion in a
try/finally block, and call child.terminate() in finally only when
child.is_alive() remains true.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not taken: join(120) is followed by an exitcode assertion, so a process that outlived the join fails the test rather than leaking silently.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@vboussot, agreed—the exitcode assertion makes a timeout an explicit test failure rather than a silent pass. I’ll leave this as-is.

vboussot added 2 commits July 29, 2026 17:58
14 of the 16 findings verified real, each fixed at its site: the post-turn
state/moves now reach the client even when deriving them raises; a stale
interrupt flag cannot swallow the next turn's error; two nested app runs are
told apart by the app hash, not the literal 'Statistics'; the transcript is
never adopted mid-turn; a GPU whose VRAM read failed no longer charts as 0%;
asyncio.timeout gives way to wait_for (the package supports 3.10); compare_runs
resolves a metric's direction newest-declaration-first like the leaderboard; a
devnull failure no longer skips the fd-1/2 detach, in the job and the runner
both; hidden files are not dataset entries; ignored_files is capped; two docs
say what the app tools actually take; STAGE_ACTIONS gains a drift test and the
five app tools a client-level schema test.

The two remaining findings do not hold: the per-case store IS the leaf its
membership checks, and the spawn helper cannot leak a process past its join.

@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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
konfai-mcp/konfai_mcp/runner.py (1)

61-69: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fail closed after stdout/stderr detachment failure in both runners.

Both implementations log fd-1/2 failures and continue execution, allowing child output to reach the MCP JSON-RPC channel.

  • konfai-mcp/konfai_mcp/runner.py#L61-L69: abort the child invocation after logging the failure.
  • konfai-mcp/konfai_mcp/server_jobs.py#L61-L66: raise or mark the job launch failed after logging the failure.
🤖 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 `@konfai-mcp/konfai_mcp/runner.py` around lines 61 - 69, Fail closed when
stdout/stderr detachment fails: in konfai-mcp/konfai_mcp/runner.py lines 61-69,
update the OSError handler around the child stdio detachment to log the failure
and abort before the target invocation in runner execution; in
konfai-mcp/konfai_mcp/server_jobs.py lines 61-66, likewise log the detachment
failure and raise or mark the job launch failed so execution cannot continue
with MCP output exposed.
konfai-mcp/konfai_mcp/server_jobs.py (1)

474-479: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Track cancellation intent instead of inferring it from signal number.

Any external SIGTERM/SIGINT is currently reported as killed, contradicting the payload contract that manual/external kills are errors. Record cancellation intent before sending the signal, or persist a cancellation token that resolves the early-exit race.

🤖 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 `@konfai-mcp/konfai_mcp/server_jobs.py` around lines 474 - 479, Update the
cancellation path that sends SIGTERM/SIGINT to persist cancellation intent
before signaling the process, using the job’s existing cancellation state or
token so early exits are covered. In the completion logic around
job.cancel_requested and returncode, classify a run as "killed" only when
recorded cancellation intent exists; treat externally delivered SIGTERM/SIGINT
as an error according to the payload contract.
♻️ Duplicate comments (1)
konfai-mcp/konfai_mcp/metrics_service.py (1)

302-313: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Centralize and stabilize metric-direction resolution.

The two paths still resolve the “current” direction independently. Equal updated_at values leave max(...) and >= dependent on input/file ordering, while a newest file with no declaration can make compare_runs_payload reuse an older declaration that leaderboard_payload does not use. Extract one resolver that returns the direction, source, and warnings, with a stable timestamp/path tie-breaker while preserving explicit overrides.

Also applies to: 438-450

🤖 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 `@konfai-mcp/konfai_mcp/metrics_service.py` around lines 302 - 313, Centralize
metric-direction selection in a shared resolver used by both leaderboard_payload
and compare_runs_payload, returning the resolved direction, source, and
warnings. In the resolver, preserve explicit direction overrides, select
declarations deterministically using updated_at plus a stable path/file
tie-breaker, and fall back consistently when the newest run has no declaration
so both callers produce identical results.
🤖 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.

Inline comments:
In `@konfai-mcp/konfai_mcp/runner.py`:
- Around line 57-58: Update the stdin redirection block in the runner’s
child-launch flow to catch `/dev/null` or fd-0 failures explicitly instead of
suppressing them; log the failure, abort the child setup, and ensure the target
is not invoked while preserving the successful `os.dup2` path.
- Around line 56-60: Update the output capture flow around the append sink in
the runner’s subprocess execution logic so the temporary capture file is bounded
or rotated while the process runs, rather than relying only on _OUTPUT_TAIL
after completion. Preserve the existing stdout/stderr redirection and
returned-tail behavior while preventing unbounded disk growth.

In `@konfai-mcp/tests/test_mcp_server_tool_index.py`:
- Around line 120-134: Update test_stage_actions_name_only_registered_tools to
invoke the public stage_actions tool through fastmcp.Client for each relevant
stage and inspect the emitted next_actions entries, asserting every action is
registered in tool_names. Keep the existing STAGE_ACTIONS constant validation as
supplemental coverage rather than the primary assertion.

---

Outside diff comments:
In `@konfai-mcp/konfai_mcp/runner.py`:
- Around line 61-69: Fail closed when stdout/stderr detachment fails: in
konfai-mcp/konfai_mcp/runner.py lines 61-69, update the OSError handler around
the child stdio detachment to log the failure and abort before the target
invocation in runner execution; in konfai-mcp/konfai_mcp/server_jobs.py lines
61-66, likewise log the detachment failure and raise or mark the job launch
failed so execution cannot continue with MCP output exposed.

In `@konfai-mcp/konfai_mcp/server_jobs.py`:
- Around line 474-479: Update the cancellation path that sends SIGTERM/SIGINT to
persist cancellation intent before signaling the process, using the job’s
existing cancellation state or token so early exits are covered. In the
completion logic around job.cancel_requested and returncode, classify a run as
"killed" only when recorded cancellation intent exists; treat externally
delivered SIGTERM/SIGINT as an error according to the payload contract.

---

Duplicate comments:
In `@konfai-mcp/konfai_mcp/metrics_service.py`:
- Around line 302-313: Centralize metric-direction selection in a shared
resolver used by both leaderboard_payload and compare_runs_payload, returning
the resolved direction, source, and warnings. In the resolver, preserve explicit
direction overrides, select declarations deterministically using updated_at plus
a stable path/file tie-breaker, and fall back consistently when the newest run
has no declaration so both callers produce identical results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0035121a-6147-4650-bc14-790a52a8bf15

📥 Commits

Reviewing files that changed from the base of the PR and between 3380b51 and 2a84614.

📒 Files selected for processing (15)
  • docs/source/usage/mcp.md
  • konfai-mcp/konfai_mcp/dataset_inspection.py
  • konfai-mcp/konfai_mcp/metrics_service.py
  • konfai-mcp/konfai_mcp/runner.py
  • konfai-mcp/konfai_mcp/server_jobs.py
  • konfai-mcp/konfai_mcp/server_support.py
  • konfai-mcp/tests/test_mcp_server_apps.py
  • konfai-mcp/tests/test_mcp_server_tool_index.py
  • konfai/utils/dataset.py
  • studio/frontend/src/App.tsx
  • studio/frontend/src/Chat.tsx
  • studio/konfai_studio/agent.py
  • studio/konfai_studio/jobs.py
  • studio/konfai_studio/server.py
  • studio/tests/test_live_feed.py
🚧 Files skipped from review as they are similar to previous changes (11)
  • docs/source/usage/mcp.md
  • konfai-mcp/konfai_mcp/server_support.py
  • konfai/utils/dataset.py
  • studio/konfai_studio/jobs.py
  • studio/konfai_studio/agent.py
  • konfai-mcp/tests/test_mcp_server_apps.py
  • konfai-mcp/konfai_mcp/dataset_inspection.py
  • studio/frontend/src/App.tsx
  • studio/konfai_studio/server.py
  • studio/frontend/src/Chat.tsx
  • studio/tests/test_live_feed.py

Comment on lines +56 to +60
with open(output_path, "ab") as sink:
with contextlib.suppress(OSError), open(os.devnull, "rb") as devnull:
os.dup2(devnull.fileno(), 0) # a jailed /dev/null must not cost fds 1/2 their detach
os.dup2(sink.fileno(), 1)
os.dup2(sink.fileno(), 2)

@coderabbitai coderabbitai Bot Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound the capture file, not only the returned tail.

_OUTPUT_TAIL limits how much is read after completion, but the append sink can grow without bound while a verbose training process runs. Use a bounded or rotating sink to prevent temporary-disk exhaustion.

Also applies to: 143-164

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 56-56: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(os.devnull, "rb")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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 `@konfai-mcp/konfai_mcp/runner.py` around lines 56 - 60, Update the output
capture flow around the append sink in the runner’s subprocess execution logic
so the temporary capture file is bounded or rotated while the process runs,
rather than relying only on _OUTPUT_TAIL after completion. Preserve the existing
stdout/stderr redirection and returned-tail behavior while preventing unbounded
disk growth.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not taken: this sink only ever holds a validation/smoke-test/import child, which the caller bounds with timeout_s and kills at expiry, and _drain removes the directory after every call — the file cannot outlive its bounded child. Training output goes through the job path and its log, not this sink; bar spam there is folded upstream by the console mirror now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@vboussot, understood. The timeout plus guaranteed _drain() cleanup bounds the sink’s lifetime, and the separate training-job log path means my training-output framing was incorrect. That makes the reported Major severity disproportionate for this capture path.

Strictly, a timeout does not impose a byte-size limit—an unusually noisy import/validation child could still consume substantial temporary storage before expiry—but given the narrow, short-lived scope, I’m fine treating a rotating sink as a non-blocking hardening improvement rather than a required change for this PR.


✏️ Learnings added
Learnt from: vboussot
Repo: fideus-labs/KonfAI PR: 71
File: konfai-mcp/konfai_mcp/runner.py:56-66
Timestamp: 2026-07-29T16:46:46.269Z
Learning: In `konfai-mcp/konfai_mcp/runner.py`, the `run_api_in_subprocess` capture sink is only used by validation, smoke-test, and import subprocesses. Training output uses the separate job logging path, where progress-bar spam is folded upstream by the console mirror.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread konfai-mcp/konfai_mcp/runner.py Outdated
Comment thread konfai-mcp/tests/test_mcp_server_tool_index.py
Left inherited after a failed devnull open, fd 0 could feed a stray read the
client's own protocol bytes. Closed, the read fails instantly instead.
@vboussot

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vboussot
vboussot merged commit e5f8cdf into main Jul 29, 2026
26 of 34 checks passed
@vboussot
vboussot deleted the feat/mcp-restore-app-exec branch July 29, 2026 17:04
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