Skip to content

[AI-3259] Judge only prompts Claude would raise and warm the judge scan - #1502

Merged
alexeyzimarev merged 1 commit into
mainfrom
capacitor/agent-b7f38e6affe64d
Oct 16, 2026
Merged

alexeyzimarev merged 1 commit into
mainfrom
capacitor/agent-b7f38e6affe64d

Conversation

@alexeyzimarev

@alexeyzimarev alexeyzimarev commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

AI-3259 — no GitHub issue exists.

What & why

The judge stands in for a human at a prompt, but PreToolUse consulted it in every permission mode, and a hook allow overrides the mode (probed on Claude Code 2.1.301: under dontAsk it ran a call the mode refuses). PreToolUse now consults only in default/acceptEdits; PermissionRequest, which fires only for a real prompt, consults in any mode; neither ever answers AskUserQuestion or ExitPlanMode, hosted bridge included. The watcher reads a judged session's transcript backlog at start, so the first consultation of a long resumed session is incremental. [Request interrupted by user…] lines are not declared as turns: Claude Code writes them, the human's no is already a declared refusal.

Where to look

  • kcap-server must change in lockstep: UserPromptDisplay.IsHarnessPayload should treat both interrupt markers as harness payloads. Until then a consultation whose newest user line is a marker fails the anchor check and runs windowless.
  • Probed: subagent calls carry agent_id with the main transcript_path, so they already reach the judge with the session's turns (now pinned). Rendered sessions stay tighten-only at PreToolUse; the daemon bridge judges their prompts.
  • Not changed: compaction summaries (isCompactSummary) still count as human turns on both sides, and the ACP bridge.

Verification

Before/after on a real 26 MB transcript (+69 MB of subagent transcripts), each read in a new process, 3 runs:

first hook read first hook with a turn window
before 97–118 ms, windowless, refusals incomplete 6th call
after (warm 183–211 ms in the watcher) 47–48 ms, 16 turns, complete 1st call

The ~0.7 s cold read reported earlier did not reproduce on this machine with a warm page cache.

  • Every new guard mutation-verified: breaking it fails its tests.
  • Core unit 4613 / 0 failed. CLI unit 6681 / 1 failed, which passes alone. Daemon unit 4175 / 2 failed; both read this hosted agent's own env (KCAP_DAEMON_ID, AGY_ADC_AUTH), and one passes with them unset.
  • dotnet publish -c Release: no IL2026/IL3050; scripts/check-linear-ids.sh clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Behavior Updates
    • The policy judge is consulted before tool calls only when Claude would prompt. Calls in auto, dontAsk, bypassPermissions, and plan modes aren’t checked unless Claude raises a prompt.
    • The judge doesn’t evaluate human-interaction tools, including user questions and plan approvals.
    • Transcript interruption markers are excluded from human-message history.
    • Session transcript history is prepared for policy checks as sessions start.
  • Documentation
    • Clarified when the policy judge is consulted and that it can’t answer Claude’s questions or approve plans.

@linear-code

linear-code Bot commented Oct 15, 2026

Copy link
Copy Markdown

AI-3259

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T20:08:08.572809Z 4e58bb6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 6144c43e-e39c-403b-8f78-3f4938700e5c
📥 Commits

Reviewing files that changed from the base of the PR and between 4e58bb6 and 4b7cdee.

📒 Files selected for processing: 2
  • README.md
  • src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

📝 Walkthrough

The Claude policy judge now observes permission modes and excludes human-interaction tools. Transcript declaration state can be warmed when a Claude session watcher starts. Documentation and tests describe and verify these changes.

Changes: Claude judge flow

Layer / File(s) Summary
Transcript reader state
src/Capacitor.Cli.Core/Harness/Claude/ClaudeHarnessPayload.cs, src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs, test/Capacitor.Cli.Core.Tests.Unit/Harness/Claude/ClaudeJudgeDeclarationReaderTests.cs
The reader recognizes exact interruption markers, reports whether transcript bytes remain unread, and supports repeated reads during warmup. Tests cover marker handling, catch-up, and unreadable transcripts.
Judge consultation rules
src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeLane.cs, src/Capacitor.Cli/Harness/Claude/ClaudePolicySeam.cs, src/Capacitor.Cli.Daemon/Harness/Claude/ClaudeHostedPolicySeam.cs, src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs, test/Capacitor.Cli.Tests.Unit/Harness/Claude/ClaudePolicySeamJudgeTests.cs, test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Claude/ClaudeHostedPolicySeamJudgeTests.cs, README.md
Consultation now depends on permission mode and tool type. Both seams use the session-specific reader state path. Tests cover permission modes, human-interaction tools, and subagent requests; the README describes the consultation conditions.
Watcher transcript warmup
src/Capacitor.Cli/Commands/WatchCommand.cs, src/Capacitor.Cli/Harness/Claude/ClaudeJudgeWarmup.cs, src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs, test/Capacitor.Cli.Tests.Unit/Harness/Claude/ClaudeJudgeWarmupTests.cs
Claude session watchers start warmup in the background. Warmup checks for a judge-enabled policy snapshot and reads the transcript. Policy-state eviction uses the reader’s session state path. Tests cover snapshot discovery and session ID forms.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant WatchCommand
  participant ClaudeJudgeWarmup
  participant PolicySnapshot
  participant ClaudeJudgeDeclarationReader
  participant ClaudePolicySeam
  WatchCommand->>ClaudeJudgeWarmup: start background warmup
  ClaudeJudgeWarmup->>PolicySnapshot: check for a frozen session snapshot
  PolicySnapshot-->>ClaudeJudgeWarmup: return judge-enabled snapshot
  ClaudeJudgeWarmup->>ClaudeJudgeDeclarationReader: warm transcript state
  ClaudeJudgeDeclarationReader-->>ClaudeJudgeWarmup: return read count
  ClaudePolicySeam->>ClaudeJudgeDeclarationReader: read declarations from session state
Loading

Suggested reviewers: realtonyyoung

Merge Risk: ⚪ Minimal · up to 4b7cd

Judge routing remains limited to eligible prompts. A startup race can omit transcript context, but does not itself bypass Claude’s permission handling; no established issue blocks merging.

Security architecture risk: 🔵 Low · up to 4b7cd

Automated approval is narrowed rather than expanded. The remaining uncertainty is whether client and server transcript handling will be deployed compatibly; a mismatch could remove context from approval decisions.

Retained concerns

  • Low · security · inferred: The client now excludes interrupt markers from declared human turns. Its documented server contract requires matching filtering; with an older predicate, a consultation following an interruption can lose its transcript window. This is a conditional mixed-version compatibility concern, not an observed production mismatch or verified authorization bypass.
Security review details

Security Blast Radius

  • observed — The demonstrated authority surface is tool approval in local and hosted sessions with an enabled session policy. The hosted interactive path obtains an attributed policy snapshot before evaluation; warmup itself only reads transcript data and writes local scan state.

Security Findings and Attack Paths

  • inferred — The supported residual scenario is loss of approval context under incompatible interrupt-marker classification. A resulting unsafe decision was not established: the remote decision behavior is unavailable, and a non-decision does not itself produce an allow.

Trust Boundaries and Controls

  • observed — An allow result can answer the tool permission request, so consultation eligibility is an authority boundary. Local non-decisions emit no allow; at PermissionRequest they leave the prompt standing. Hosted non-decisions continue to the human permission lane. A previously journaled ask also prevents a fresh allow from answering that local prompt.

Resilience and Maintainability Implications

  • observed — Session-end attempts a capped watcher stop before cache eviction, and watcher termination waits for process exit before escalating to a kill. This is counterevidence to inevitable cache recreation after cleanup. Cleanup remains best-effort, while the detached warmup task has no explicit completion join.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 53.49% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 43 functions across 12 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed 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 concisely summarizes the main changes: limiting judge consultations to prompts Claude would raise and warming the judge scan.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 53.49% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 43 functions across 12 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Judge only Claude-raised prompts and warm session transcripts

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Consult the judge only where Claude may raise a permission prompt, preserving native modes and
 human approvals.
• Warm judged-session transcript scans so resumed sessions have context on their first consultation.
• Exclude interruption markers from human turns and test the new consultation boundaries.
Diagram

graph TD
  W["Claude watcher"] --> S{"Judge enabled?"} -->|yes| R["Transcript scan"] --> C["Shared scan state"] --> J["Policy judge"]
  P["PreToolUse"] --> G{"Eligible prompt?"} -->|yes| J
  H["Raised prompts"] --> G
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Consult only at PermissionRequest
  • ➕ Consultations would occur only after Claude confirms a prompt exists.
  • ➕ Permission-mode eligibility would not need to be inferred at PreToolUse.
  • ➖ The judge could not decide eligible calls early at PreToolUse.
  • ➖ Calls that reach only the PreToolUse seam would lose that consultation opportunity.

Recommendation: Keep the two-seam approach: mode gating prevents premature judge allows, while PermissionRequest still handles prompts raised in any mode. The shared scan state and watcher warm-up preserve first-consultation context without putting a backlog scan on the hook path. Coordinate the interrupt-marker predicate with kcap-server, as noted in the PR, to avoid windowless consultations.

Files changed (13) +377 / -11

Enhancement (3) +80 / -1
ClaudeJudgeDeclarationReader.csAdd bounded transcript warm-up and shared state path +36/-1

Add bounded transcript warm-up and shared state path

• Adds a session-state path helper and a warm-up loop that advances the existing scanner through a backlog, capped at 64 reads. Subsequent consultations resume from the persisted cursor.

src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs

WatchCommand.csStart Claude transcript warm-up in the watcher +4/-0

Start Claude transcript warm-up in the watcher

• Launches background judge warm-up for main Claude sessions, outside the hook consultation path.

src/Capacitor.Cli/Commands/WatchCommand.cs

ClaudeJudgeWarmup.csWarm judged-session transcript state after snapshot creation +40/-0

Warm judged-session transcript state after snapshot creation

• Retries loading the frozen snapshot before scanning, and skips sessions without an enabled judge. Warm-up failures leave normal consultation behavior intact.

src/Capacitor.Cli/Harness/Claude/ClaudeJudgeWarmup.cs

Bug fix (4) +50 / -7
ClaudeHarnessPayload.csExclude Claude interruption markers from human turns +12/-0

Exclude Claude interruption markers from human turns

• Treats both standalone interruption markers as harness payloads without excluding messages that merely quote them. The matching server-side predicate must change in lockstep for turn-window anchors to agree.

src/Capacitor.Cli.Core/Harness/Claude/ClaudeHarnessPayload.cs

ClaudeJudgeLane.csDefine Claude judge eligibility rules +24/-0

Define Claude judge eligibility rules

• Centralizes the exclusions for AskUserQuestion and ExitPlanMode and limits PreToolUse judging to default, acceptEdits, or absent permission modes.

src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeLane.cs

ClaudeHostedPolicySeam.csKeep hosted human-interaction prompts out of the judge +2/-2

Keep hosted human-interaction prompts out of the judge

• Prevents the hosted bridge from consulting the judge for questions or plan approvals. Uses the shared session-state path when reading transcript declarations.

src/Capacitor.Cli.Daemon/Harness/Claude/ClaudeHostedPolicySeam.cs

ClaudePolicySeam.csGate local judge consultations by prompt eligibility +12/-5

Gate local judge consultations by prompt eligibility

• Reads Claude's permission mode and applies the judge-lane rules at PreToolUse while allowing PermissionRequest to consult for an actual raised prompt. Excludes human-interaction tools at both seams and uses the shared scan-state path.

src/Capacitor.Cli/Harness/Claude/ClaudePolicySeam.cs

Refactor (1) +1 / -1
ClaudeHookCommand.csUse the shared judge-state path during cleanup +1/-1

Use the shared judge-state path during cleanup

• Session cleanup now deletes the cursor file through the same path helper used by warm-up and consultations.

src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs

Tests (4) +245 / -1
ClaudeJudgeDeclarationReaderTests.csTest interruption filtering and backlog warm-up +50/-0

Test interruption filtering and backlog warm-up

• Verifies that standalone markers are not human turns, quoted markers remain human messages, and warm-up makes a multi-read backlog available to the next incremental consultation.

test/Capacitor.Cli.Core.Tests.Unit/Harness/Claude/ClaudeJudgeDeclarationReaderTests.cs

ClaudeHostedPolicySeamJudgeTests.csTest hosted question and plan exclusions +17/-0

Test hosted question and plan exclusions

• Confirms the hosted bridge neither consults the judge nor answers AskUserQuestion and ExitPlanMode prompts.

test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Claude/ClaudeHostedPolicySeamJudgeTests.cs

ClaudeJudgeWarmupTests.csTest watcher warm-up snapshot and state behavior +79/-0

Test watcher warm-up snapshot and state behavior

• Covers enabled, disabled, missing, and late-frozen snapshots, plus state-path consistency for dashed session IDs.

test/Capacitor.Cli.Tests.Unit/Harness/Claude/ClaudeJudgeWarmupTests.cs

ClaudePolicySeamJudgeTests.csTest local judge lanes and subagent context +99/-1

Test local judge lanes and subagent context

• Checks PreToolUse mode gating, consultation at raised prompts, and exclusion of questions and plan approvals at both seams. Also verifies subagent consultations retain the session's declared human turns.

test/Capacitor.Cli.Tests.Unit/Harness/Claude/ClaudePolicySeamJudgeTests.cs

Documentation (1) +1 / -1
README.mdClarify when the judge stands in for a user +1/-1

Clarify when the judge stands in for a user

• Documents that PreToolUse does not consult the judge in non-prompting modes, while prompts raised in those modes can still be judged. Clarifies that questions and plan approvals remain with the user.

README.md

@qodo-code-review

qodo-code-review Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 🔗 Cross-repo conflicts (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Interrupts break server judge context 🔗 Cross-repo conflict ≡ Correctness
Description
ClaudeHarnessPayload.IsHarnessPayload now excludes both interrupt markers from declared turns, but
kcap-server’s UserPromptDisplay.IsHarnessPayload still treats them as human messages. When a
marker appears in the consultation window, the server’s turn-anchor check fails and the judge runs
without the session’s turn context.
Code

src/Capacitor.Cli.Core/Harness/Claude/ClaudeHarnessPayload.cs[32]

+        if (InterruptMarkers.Contains(text.Trim(), StringComparer.Ordinal)) return true;
Relevance

●●● Strong

The finding identifies a concrete cross-component correctness mismatch; closely related Claude
transcript fixes were accepted.

PR-#1318

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new CLI predicate excludes the markers when constructing declared turns. The server’s
human-message predicate lacks that exclusion, so its evidence builder detects an undeclared human
message or an anchor ahead of the CLI’s last declared turn and falls back to windowless evidence.

kcap-cli -> kcap-server
src/Capacitor.Cli.Core/Harness/Claude/ClaudeHarnessPayload.cs[18-32]
src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[230-241]
External repo: kurrent-io/kcap-server, src/Capacitor.Server.Core/UserPromptDisplay.cs [16-49]
External repo: kurrent-io/kcap-server, src/Capacitor.Server.Services/Sessions/HumanUserMessage.cs [9-14]
External repo: kurrent-io/kcap-server, src/Capacitor.Server.Services/Policy/Judge/PolicyJudgeEvidenceBuilder.cs [84-106]

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 CLI excludes Claude interrupt markers from judge turns, while kcap-server still counts them as human turns. This makes affected consultations windowless.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeHarnessPayload.cs[18-32]
- /cross_repos/kcap-server/src/Capacitor.Server.Core/UserPromptDisplay.cs[16-49]

## Recommended Fix
Update kcap-server’s harness-payload predicate to recognize both exact interrupt markers, test its agreement with the CLI predicate, and coordinate deployment of the two changes.

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

Dismiss ↗ | View ↗


2. Resumed sessions miss the first warm scan ✓ Resolved
Description
ClaudeJudgeWarmup.RunAsync looks up a dashless session key, but the session-start hook freezes the
snapshot under the dashed identifier from its JSON body. For a dashed session ID, the watcher cannot
find that snapshot until a later hook creates a dashless one, so it may finish its retries before
the first consultation and leave the transcript backlog unread.
Code

src/Capacitor.Cli/Harness/Claude/ClaudeJudgeWarmup.cs[R25-30]

+            var key = sessionId.Replace("-", "");
+            var store = new PolicySnapshotStore(config);
+
+            foreach (var wait in snapshotWaits ?? SnapshotWaits) {
+                if (wait > TimeSpan.Zero) await Task.Delay(wait, time, ct);
+                if (store.TryLoad(key) is not { } snapshot) continue;
Relevance

●● Moderate

The identifier mismatch is plausible, but the current tests and implementation provide conflicting
evidence about snapshot lookup behavior.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Session-start reads the unmodified JSON session ID and passes it to both watcher startup and
snapshot freezing. The watcher command removes dashes before warm-up receives the ID; the snapshot
store's sanitizer preserves dashes, so the two snapshot paths differ. The early hook parse and
PreToolUse path use the dashless ID, explaining why a matching snapshot can appear only on a
subsequent hook.

src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs[709-725]
src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs[754-758]
src/Capacitor.Cli/Program.cs[841-870]
src/Capacitor.Cli.Core/Policy/PolicySnapshotStore.cs[88-91]
src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs[187-194]
src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs[251-265]

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 watcher looks for a dashless snapshot, while session-start saves its snapshot under the dashed identifier it reads from the hook body. Warm-up can give up before a later hook creates a dashless snapshot.
## Fix Focus Areas
- src/Capacitor.Cli/Harness/Claude/ClaudeJudgeWarmup.cs[25-33]
- src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs[709-725]
- src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs[754-758]
## Recommended Fix
Use one canonical session key for snapshot creation, watcher lookup, and judge scan state. Add a test that follows a dashed session ID from session-start through watcher warm-up before any tool hook runs.

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



Remediation recommended

3. The commit subject lacks a GitHub issue ⊘ Outdated
Description
The supplied commit subject Judge only prompts Claude would raise and warm the judge scan does not
end with a GitHub reference in the required (#digits) form. The subject therefore fails the
repository's merge-subject format even though its wording is otherwise a single clause.
Code

README.md[365]

+**The judge.** Set `judge: { mode: unmatched, prompt: "…" }` and a call no rule decides in a Claude Code session, local or hosted, is sent to your Capacitor server's policy judge — an LLM classifier whose model and keys stay on the server — which answers allow, deny or ask. It stands in only where Claude would ask you: it is not consulted before a call in `auto`, `dontAsk`, `bypassPermissions` or `plan` mode (a prompt one of them does raise still reaches it), and never answers a question Claude asks you or a plan it puts to you for approval. It sees your `prompt`, the action, your recent messages in this session and every call you already refused at a prompt, so "push when the tests pass" two turns ago can approve the push, and a push you said no to stays a prompt. An ACP-routed hosted agent (Cursor, Copilot, Kiro, Gemini, OpenCode) consults it too, but has no transcript the server can read, so the judge sees only the calls you refused through the daemon, not your messages. The judge has 2 seconds in a local session and 5 in a hosted one, where the agent is already waiting on a prompt; anything it does not decide in time, an `uncertain` answer, an unreachable server or one without the judge enabled all **pass through** to the agent's native behavior (in a hosted session, the permission card), and every consultation is recorded beside the rule decisions.
Relevance

●●● Strong

The repository rule explicitly requires a trailing GitHub issue reference, and this subject omits
one.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The supplied commit subject has no trailing (#<digits>) reference, so it does not match the
required subject pattern.

Rule 2897961: Enforce single-clause imperative commit subject with GitHub issue reference and 80-char limit

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 commit subject has no trailing GitHub issue reference in the required `(#digits)` format.

## Fix Focus Areas
- README.md[365-365]

## Recommended Fix
Use a single imperative subject ending with an explicit GitHub issue reference, or revise the checklist/process if this PR intentionally has no GitHub issue.

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


4. Concurrent scans can lose saved progress 🐞 Bug ➹ Performance
Description
Warm repeatedly reads and replaces the same scanner state file used by hook consultations, without
coordinating their load-and-save operations. If a hook saves a newer cursor after warm-up loads an
older state but before warm-up saves it, the next consultation must rescan transcript bytes it had
already processed, potentially losing the intended first-call speedup.
Code

src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[R72-74]

+        while (more && reads < MaxWarmReads && !ct.IsCancellationRequested) {
+            Read(transcriptPath, null, cwd, statePath, maxBytesPerRead, out more);
+            reads++;
Relevance

●● Moderate

The shared state race is plausible, but history supports bounded scanning more directly than
concurrent state coordination.

PR-#1318

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new watcher task runs concurrently with hooks and calls the stateful reader in a loop. Both
paths load their own copy and unconditionally replace the same state path, so atomic file
replacement does not prevent a later stale write from reverting a cursor.

src/Capacitor.Cli/Commands/WatchCommand.cs[357-361]
src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[69-74]
src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[85-104]
src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[349-359]
src/Capacitor.Cli/Harness/Claude/ClaudePolicySeam.cs[238-242]

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

## Issue description
Warm-up and consultations independently load and replace one scanner state file. A warm-up write based on an older load can overwrite cursor progress saved by a hook.
## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[69-74]
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[85-104]
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[349-359]
## Recommended Fix
Serialize scan-state read/modify/write operations across watcher and hook processes, or reject a stale write and retry against the newer state. Test an interleaving in which a consultation saves while warm-up is scanning.

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

Dismiss ↗ | View ↗



Informational

5. The PR title contains a Linear ID ⊘ Outdated
Description
The pull request title is [AI-3259] Judge only prompts Claude would raise and warm the judge scan,
so it embeds a Linear issue key before the imperative subject. This violates the title convention
even though the changed implementation itself does not depend on that identifier.
Code

README.md[365]

+**The judge.** Set `judge: { mode: unmatched, prompt: "…" }` and a call no rule decides in a Claude Code session, local or hosted, is sent to your Capacitor server's policy judge — an LLM classifier whose model and keys stay on the server — which answers allow, deny or ask. It stands in only where Claude would ask you: it is not consulted before a call in `auto`, `dontAsk`, `bypassPermissions` or `plan` mode (a prompt one of them does raise still reaches it), and never answers a question Claude asks you or a plan it puts to you for approval. It sees your `prompt`, the action, your recent messages in this session and every call you already refused at a prompt, so "push when the tests pass" two turns ago can approve the push, and a push you said no to stays a prompt. An ACP-routed hosted agent (Cursor, Copilot, Kiro, Gemini, OpenCode) consults it too, but has no transcript the server can read, so the judge sees only the calls you refused through the daemon, not your messages. The judge has 2 seconds in a local session and 5 in a hosted one, where the agent is already waiting on a prompt; anything it does not decide in time, an `uncertain` answer, an unreachable server or one without the judge enabled all **pass through** to the agent's native behavior (in a hosted session, the permission card), and every consultation is recorded beside the rule decisions.
Relevance

● Weak

Recent repository guidance explicitly permits Linear prefixes in titles; PR #659 documents the
expected title form.

PR-#659

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The supplied PR title begins with [AI-3259], while the checklist requires an imperative title
without explicit issue references.

Rule 2897978: Format PR titles as imperative commit subjects without explicit issue references

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 pull request title contains the Linear issue reference `[AI-3259]`, which is prohibited in PR titles.

## Fix Focus Areas
- README.md[365-365]

## Recommended Fix
Rename the pull request to an imperative subject without any issue identifier, such as `Judge only prompts Claude would raise and warm the judge scan`.

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


6. The PR has no linked GitHub issue ⊘ Outdated
Description
The PR's reference line is AI-3259 — no GitHub issue exists. and contains no GitHub closing
keyword followed by an issue number. The Linear key is present, but the required GitHub and Linear
references are not supplied together in the required reference format.
Code

README.md[365]

+**The judge.** Set `judge: { mode: unmatched, prompt: "…" }` and a call no rule decides in a Claude Code session, local or hosted, is sent to your Capacitor server's policy judge — an LLM classifier whose model and keys stay on the server — which answers allow, deny or ask. It stands in only where Claude would ask you: it is not consulted before a call in `auto`, `dontAsk`, `bypassPermissions` or `plan` mode (a prompt one of them does raise still reaches it), and never answers a question Claude asks you or a plan it puts to you for approval. It sees your `prompt`, the action, your recent messages in this session and every call you already refused at a prompt, so "push when the tests pass" two turns ago can approve the push, and a push you said no to stays a prompt. An ACP-routed hosted agent (Cursor, Copilot, Kiro, Gemini, OpenCode) consults it too, but has no transcript the server can read, so the judge sees only the calls you refused through the daemon, not your messages. The judge has 2 seconds in a local session and 5 in a hosted one, where the agent is already waiting on a prompt; anything it does not decide in time, an `uncertain` answer, an unreachable server or one without the judge enabled all **pass through** to the agent's native behavior (in a hosted session, the permission card), and every consultation is recorded beside the rule decisions.
Relevance

● Weak

PR guidance allows omitting nonexistent issue references when explicitly stated, matching this
description.

PR-#659

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The supplied PR description begins with AI-3259 — no GitHub issue exists. rather than a single
line containing a GitHub closing keyword and issue reference together with the Linear key.

Rule 2897991: PR description must contain both GitHub and Linear issue references; PR title must not contain issue IDs

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 PR description does not provide a reference line containing both a GitHub closing reference such as `Closes #123` and a Linear issue key.

## Fix Focus Areas
- README.md[365-365]

## Recommended Fix
Add one dedicated reference line containing exactly one GitHub closing keyword with an issue number and the Linear key, or update the process for PRs where no GitHub issue exists.

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


Grey Divider

Context sources
✅ Compliance rules (platform): 63 rules
✅ Cross-repo context — repo relationships
  Explored: repo: kurrent-io/kcap-server (sha: 2c829367) — View relationship
Review mode: Auto: 🧠 Deep: Multiple independent hook, mode, transcript, and warmup paths create defect density.

Grey Divider

Tip of the day
💡 Did you know, you can pick a display mode instead of tweaking every Display option by hand

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread README.md
Comment thread src/Capacitor.Cli/Harness/Claude/ClaudeJudgeWarmup.cs
Comment on lines +72 to +74
while (more && reads < MaxWarmReads && !ct.IsCancellationRequested) {
Read(transcriptPath, null, cwd, statePath, maxBytesPerRead, out more);
reads++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

4. Concurrent scans can lose saved progress 🐞 Bug ➹ Performance

Warm repeatedly reads and replaces the same scanner state file used by hook consultations, without
coordinating their load-and-save operations. If a hook saves a newer cursor after warm-up loads an
older state but before warm-up saves it, the next consultation must rescan transcript bytes it had
already processed, potentially losing the intended first-call speedup.
Agent Prompt
## Issue description
Warm-up and consultations independently load and replace one scanner state file. A warm-up write based on an older load can overwrite cursor progress saved by a hook.
## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[69-74]
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[85-104]
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeJudgeDeclarationReader.cs[349-359]
## Recommended Fix
Serialize scan-state read/modify/write operations across watcher and hook processes, or reject a stale write and retry against the newer state. Test an interleaving in which a consultation saves while warm-up is scanning.

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

Dismiss ↗ | View ↗


if (IsWhollyTaskNotification(text) || ClaudeAgentMessageFrame.IsMatch(text)) return true;

if (InterruptMarkers.Contains(text.Trim(), StringComparer.Ordinal)) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Interrupts break server judge context 🔗 Cross-repo conflict ≡ Correctness

ClaudeHarnessPayload.IsHarnessPayload now excludes both interrupt markers from declared turns, but
kcap-server’s UserPromptDisplay.IsHarnessPayload still treats them as human messages. When a
marker appears in the consultation window, the server’s turn-anchor check fails and the judge runs
without the session’s turn context.
Agent Prompt
## Issue description
The CLI excludes Claude interrupt markers from judge turns, while kcap-server still counts them as human turns. This makes affected consultations windowless.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Claude/ClaudeHarnessPayload.cs[18-32]
- /cross_repos/kcap-server/src/Capacitor.Server.Core/UserPromptDisplay.cs[16-49]

## Recommended Fix
Update kcap-server’s harness-payload predicate to recognize both exact interrupt markers, test its agreement with the CLI predicate, and coordinate deployment of the two changes.

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

Dismiss ↗ | View ↗

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e58bb6424

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +22 to +23
public static bool PromptsAtPreToolUse(string? permissionMode) =>
permissionMode is null or "default" or "acceptEdits";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip auto-approved calls in the PreToolUse judge lane

This mode-only predicate still consults the judge for calls Claude would never prompt for: in acceptEdits, Edit, Write, and common filesystem commands are automatically accepted, while in default mode read-only commands and calls covered by native allow rules also run without approval. The official Claude Code permission documentation confirms these exemptions. Because PreToolUse runs before native permission evaluation, an unmatched call in these categories can now receive a judge deny or ask, defeating the stated human-prompt-only behavior; this lane must also account for the action/tool's native auto-approval or defer consultation until PermissionRequest.

Useful? React with 👍 / 👎.

A hook allow overrides the permission mode, so the judge is not consulted
where Claude asks nobody, nor on a question or plan approval. Interrupt
markers are not declared as turns: kcap-server's harness-payload filter
must drop them in lockstep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alexeyzimarev
alexeyzimarev force-pushed the capacitor/agent-b7f38e6affe64d branch from 4e58bb6 to 4b7cdee Compare October 16, 2026 07:01
@alexeyzimarev
alexeyzimarev merged commit e7be8e4 into main Oct 16, 2026
10 checks passed
@alexeyzimarev
alexeyzimarev deleted the capacitor/agent-b7f38e6affe64d branch October 16, 2026 07:50
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