Skip to content

Probe repo-local skill discovery and startup ordering across harnesses - #975

Open
alexeyzimarev wants to merge 42 commits into
mainfrom
alexeyzimarev/ai-2829-verify-repo-local-skill-discovery-and-startup-ordering
Open

alexeyzimarev wants to merge 42 commits into
mainfrom
alexeyzimarev/ai-2829-verify-repo-local-skill-discovery-and-startup-ordering

Conversation

@alexeyzimarev

@alexeyzimarev alexeyzimarev commented Sep 16, 2026

Copy link
Copy Markdown
Member

Refs #961 — AI-2829 (pass 1 of 2; the pass 2 PR closes the issue)

What & why

Repeatable probes under docs/probes/2026-09-16-skills-discovery/ measure, per harness and launch mode, whether a repo-local skill written by a startup hook reaches the first model request, which Git exclusion keeps it loadable, and which vendor directories each harness reads. The verdict is the starting session's own reply: a token that exists only in the skill body. matrix.json, capability-matrix.md and findings.md are the deliverables #778 and #962 consume; no production code changes.

Where to look

findings.md "Consequences": a hook file drop reaches the first request only on Kiro (agentSpawn runs at session creation) and on OpenCode 1.x print into an existing root; Pi's resources_discover extension route works; Cursor's fire-and-forget hook races indexing; Claude, Codex, Copilot and agy index before hooks and offer no headless reload. Both .gitignore and info/exclude keep skills loadable everywhere. Claude and Codex have no vendor-isolated destination. Gemini is untested on this account (OAuth tier refused) and OpenCode 2.0.4 loads no local plugin.

Verification

python3 docs/probes/2026-09-16-skills-discovery/selftest.py   # Ran 74 tests, OK (also under -W error::ResourceWarning)
python3 docs/probes/2026-09-16-skills-discovery/probe.py --emit   # 464 rows

Nine harnesses measured on macOS 26.6.2 arm64 (Claude 2.1.273, Codex 0.154.0, Pi 0.85.1, Copilot 1.0.85, Kiro 2.21.4, OpenCode 1.18.31 and 2.0.4, cursor-agent 2026.09.15, agy 1.2.4; Gemini 0.60.0 blocked on credentials), print and daemon modes: every verdict row carries two runs, hook stamps and the vendor's event stream.

🤖 Generated with Claude Code

alexeyzimarev and others added 30 commits September 16, 2026 11:52
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ed (#961)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#961)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 16, 2026

Copy link
Copy Markdown

AI-2829

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 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-09-16T13:56:27.001016Z 2618e43 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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add cross-harness repo-local skill discovery probes

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Adds isolated startup-order probes across supported AI harnesses and launch modes.
• Records skill visibility, Git exclusion, and cross-vendor directory consumption results.
• Documents measured limitations and Pi's working first-request registration path.
Diagram

graph TD
  CLI["Probe CLI"] --> SB["Isolated Sandbox"] --> SC["Scenarios S0-S4"] --> AD["Harness Adapters"] --> DR["Protocol Drivers"] --> VE["Verdict Engine"] --> MX[("Result Matrix")] --> RP["Generated Reports"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Reuse production harness integrations
  • ➕ Reduces duplicated vendor configuration and launch logic
  • ➕ Exercises the code paths eventually used in production
  • ➖ Makes vendor behavior inseparable from kcap behavior
  • ➖ Production changes could invalidate historical probe reproducibility
  • ➖ Conflicts with measuring vendors without production changes
2. Manual per-vendor certification
  • ➕ Requires less probe infrastructure
  • ➕ Can cover GUI and interactive modes immediately
  • ➖ Produces less repeatable evidence
  • ➖ Makes cross-vendor comparisons inconsistent
  • ➖ Cannot reliably regenerate a machine-readable matrix
3. Containerized vendor probes
  • ➕ Improves environment reproducibility
  • ➕ Further isolates host configuration and credentials
  • ➖ Keychain and browser authentication may not transfer
  • ➖ Vendor behavior may differ from native macOS execution
  • ➖ Adds substantial setup beyond this research scope

Recommendation: Keep the standard-library probe kit and hand-written vendor adapters. This best isolates vendor contracts from kcap behavior while preserving repeatable evidence; production-adapter reuse weakens that separation, while manual and containerized approaches cannot currently provide equivalent authenticated, host-faithful coverage.

Files changed (36) +14292 / -0

Enhancement (11) +1452 / -0
agy.pyAdd Antigravity CLI probe variants +66/-0

Add Antigravity CLI probe variants

• Supports both candidate skill layouts and plugin locations, installs PreInvocation hooks, and parses streamed print-mode replies.

docs/probes/2026-09-16-skills-discovery/harness/agy.py

claude.pyAdd the Claude Code probe adapter +100/-0

Add the Claude Code probe adapter

• Handles isolated or keychain-backed configuration, installs SessionStart hooks, restricts tools to Skill, and extracts JSON print replies.

docs/probes/2026-09-16-skills-discovery/harness/claude.py

codex.pyAdd print and app-server Codex probes +118/-0

Add print and app-server Codex probes

• Configures repository trust, installs SessionStart hooks, launches both Codex modes, and distinguishes native SKILL.md reads from searches.

docs/probes/2026-09-16-skills-discovery/harness/codex.py

copilot.pyAdd print and ACP Copilot probes +127/-0

Add print and ACP Copilot probes

• Injects GitHub authentication, installs sessionStart hooks, drives both launch modes, and classifies skill-tool loads separately from searches.

docs/probes/2026-09-16-skills-discovery/harness/copilot.py

cursor.pyAdd Cursor CLI and plugin-registration probes +78/-0

Add Cursor CLI and plugin-registration probes

• Defines isolated credentials, sessionStart hooks, workspaceOpen plugin registration, and print and ACP launch behavior.

docs/probes/2026-09-16-skills-discovery/harness/cursor.py

gemini.pyAdd Gemini print and ACP probes +64/-0

Add Gemini print and ACP probes

• Creates isolated authentication and trust settings, installs SessionStart hooks, drives both modes, and supports catalogue listing.

docs/probes/2026-09-16-skills-discovery/harness/gemini.py

kiro.pyAdd version-aware Kiro probe adapters +92/-0

Add version-aware Kiro probe adapters

• Supports Kiro 2.x agentSpawn and 3.x SessionStart hooks plus default, bare-agent, and explicit-skill-resource variants.

docs/probes/2026-09-16-skills-discovery/harness/kiro.py

opencode_v1.pyAdd OpenCode V1 event and registration probes +108/-0

Add OpenCode V1 event and registration probes

• Isolates OpenCode data and credentials, generates V1 plugins, and drives print or ACP sessions with optional model selection.

docs/probes/2026-09-16-skills-discovery/harness/opencode_v1.py

opencode_v2.pyAdd OpenCode V2 reload probes +72/-0

Add OpenCode V2 reload probes

• Adds standalone V2 adapters that test setup-time and prompt-hook skill registration followed by explicit skill reloads.

docs/probes/2026-09-16-skills-discovery/harness/opencode_v2.py

pi.pyAdd Pi print, RPC, and resource-registration probes +137/-0

Add Pi print, RPC, and resource-registration probes

• Installs session_start extensions, implements resources_discover registration, and classifies native skill reads independently from searches.

docs/probes/2026-09-16-skills-discovery/harness/pi.py

probe.pyOrchestrate discovery scenarios across adapters +490/-0

Orchestrate discovery scenarios across adapters

• Implements free-phase checks and S0–S4 runs with positive-control gating, resumability, reruns, disagreement handling, evidence retention, and matrix emission.

docs/probes/2026-09-16-skills-discovery/probe.py

Tests (3) +1253 / -0
fake.pyAdd a deterministic fake probe adapter +44/-0

Add a deterministic fake probe adapter

• Provides an in-process vendor substitute that executes hooks and reads known skill roots for orchestration self-tests.

docs/probes/2026-09-16-skills-discovery/harness/fake.py

selftest.pyCover probe orchestration and protocol behavior +1099/-0

Cover probe orchestration and protocol behavior

• Adds comprehensive vendor-free tests for isolation, exclusions, prompts, verdicts, recording, resumability, adapters, drivers, cleanup, and reporting.

docs/probes/2026-09-16-skills-discovery/selftest.py

selftest_servers.pyAdd fake ACP, app-server, and Pi RPC servers +110/-0

Add fake ACP, app-server, and Pi RPC servers

• Simulates vendor protocols, hook trust, tool events, skill replies, and lingering grandchildren for deterministic driver tests.

docs/probes/2026-09-16-skills-discovery/selftest_servers.py

Documentation (6) +10603 / -0
capability-matrix.mdSummarize measured skill-discovery capabilities +40/-0

Summarize measured skill-discovery capabilities

• Provides the generated per-entry table of native roots, consumed roots, startup mechanisms, exclusions, reload paths, isolation, and test status.

docs/probes/2026-09-16-skills-discovery/capability-matrix.md

findings.mdDocument cross-harness probe findings +211/-0

Document cross-harness probe findings

• Records methodology, reproduction commands, measured and blocked harnesses, cross-vendor root consumption, Git exclusion behavior, and delivery consequences.

docs/probes/2026-09-16-skills-discovery/findings.md

matrix.jsonRecord the pass-one capability evidence matrix +6314/-0

Record the pass-one capability evidence matrix

• Commits 326 aggregated rows covering versions, modes, scenarios, roots, exclusions, verdicts, run counts, and evidence paths.

docs/probes/2026-09-16-skills-discovery/matrix.json

report.pyGenerate the capability matrix report +99/-0

Generate the capability matrix report

• Summarizes matrix rows into per-entry capabilities, untested reasons, shared-root isolation, reload paths, and GUI status.

docs/probes/2026-09-16-skills-discovery/report.py

2026-09-16-ai2829-skills-discovery-probes.mdDocument the pass-one implementation plan +3627/-0

Document the pass-one implementation plan

• Provides the staged design, interfaces, test-first tasks, vendor installation steps, execution procedure, and delivery checklist.

docs/superpowers/plans/2026-09-16-ai2829-skills-discovery-probes.md

2026-09-16-skills-discovery-probes-design.mdSpecify cross-harness skill-discovery certification +312/-0

Specify cross-harness skill-discovery certification

• Defines the research problem, isolation contract, scenarios, adapters, verdict semantics, outputs, pass boundaries, and risks without production changes.

docs/superpowers/specs/2026-09-16-skills-discovery-probes-design.md

Other (16) +984 / -0
.gitignoreIgnore transient probe output and vendor logs +2/-0

Ignore transient probe output and vendor logs

• Excludes generated run evidence and stderr logs while retaining committed matrix and report artifacts.

docs/probes/2026-09-16-skills-discovery/.gitignore

__init__.pyRegister all probe harness entries +33/-0

Register all probe harness entries

• Maps CLI entry names to the Claude, Codex, Gemini, Pi, Cursor, Copilot, Kiro, OpenCode, Antigravity, and fake adapters.

docs/probes/2026-09-16-skills-discovery/harness/init.py

base.pyDefine the common harness adapter contract +85/-0

Define the common harness adapter contract

• Introduces adapter metadata, hook and reply models, vendor lifecycle methods, and shared skill-path helpers.

docs/probes/2026-09-16-skills-discovery/harness/base.py

__init__.pyInitialize the probe support package +0/-0

Initialize the probe support package

• Marks the shared probe library as an importable Python package.

docs/probes/2026-09-16-skills-discovery/lib/init.py

acp_driver.pyAdd an isolated ACP one-turn driver +127/-0

Add an isolated ACP one-turn driver

• Runs initialization, session creation, and the first prompt while capturing frames, replies, tool counts, timing, and robust process shutdown.

docs/probes/2026-09-16-skills-discovery/lib/acp_driver.py

appserver_driver.pyAdd the Codex app-server driver +126/-0

Add the Codex app-server driver

• Implements JSON-RPC startup and turn handling, seeds untrusted hook hashes, captures completed items, and records tool usage.

docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py

git_exclusion.pyImplement Git exclusion probe arms +46/-0

Implement Git exclusion probe arms

• Applies repository .gitignore or worktree-aware info/exclude rules and verifies skills remain present but absent from Git status.

docs/probes/2026-09-16-skills-discovery/lib/git_exclusion.py

hook_script.pyGenerate startup hook payloads and timestamps +48/-0

Generate startup hook payloads and timestamps

• Writes executable skill creation or deletion scripts, captures hook input without blocking launch, and records ordering evidence.

docs/probes/2026-09-16-skills-discovery/lib/hook_script.py

isolation.pyCreate isolated repositories and vendor roots +84/-0

Create isolated repositories and vendor roots

• Builds fresh committed repositories, copies only declared credentials, sanitizes process and Git environments, and manages cleanup.

docs/probes/2026-09-16-skills-discovery/lib/isolation.py

jsonl_child.pyAdd managed JSONL child-process transport +94/-0

Add managed JSONL child-process transport

• Provides frame capture, timeout handling, stderr retention, and process-group cleanup for synchronous protocol drivers.

docs/probes/2026-09-16-skills-discovery/lib/jsonl_child.py

pirpc_driver.pyAdd the Pi RPC first-turn driver +54/-0

Add the Pi RPC first-turn driver

• Sends one prompt through Pi RPC, collects assistant text and tool events, and stops on settlement, failure, or timeout.

docs/probes/2026-09-16-skills-discovery/lib/pirpc_driver.py

print_driver.pyAdd a shared headless process driver +35/-0

Add a shared headless process driver

• Runs print-mode commands with controlled input, timeout and stderr handling, optional reply extraction, and timing metadata.

docs/probes/2026-09-16-skills-discovery/lib/print_driver.py

probe_skill.pyDefine nonce skills, prompts, and reply parsing +96/-0

Define nonce skills, prompts, and reply parsing

• Generates tokens available only inside skill bodies, writes supported layouts, forbids filesystem searches, and parses model replies.

docs/probes/2026-09-16-skills-discovery/lib/probe_skill.py

procs.pyAdd process-group termination support +13/-0

Add process-group termination support

• Ensures re-executed vendors and inherited-pipe grandchildren cannot wedge driver shutdown.

docs/probes/2026-09-16-skills-discovery/lib/procs.py

recorder.pyRecord runs and aggregate matrix rows +97/-0

Record runs and aggregate matrix rows

• Persists versioned run metadata and combines repeated verdicts into stable, evidence-linked rows without merging vendor versions.

docs/probes/2026-09-16-skills-discovery/lib/recorder.py

verdict.pyDefine probe verdict and repetition rules +44/-0

Define probe verdict and repetition rules

• Classifies controls, single skills, and cross-root loads, detects prompt-design failures, and flags disagreement between repeated runs.

docs/probes/2026-09-16-skills-discovery/lib/verdict.py

@qodo-code-review

qodo-code-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Timed-out probes become false negatives ✓ Resolved 🐞 Bug ≡ Correctness
Description
print_ask catches TimeoutExpired but returns exit_code=None with no timeout note, including
when the reply is empty or incomplete. Runner._ask treats that combination as a normal response,
so verdict evaluation records not_visible rather than untested for a timed-out vendor.
Code

docs/probes/2026-09-16-skills-discovery/lib/print_driver.py[R24-27]

+        except subprocess.TimeoutExpired as ex:
+            raw = (ex.stdout or b"").decode("utf-8", "replace") if isinstance(ex.stdout, bytes) else (ex.stdout or "")
+            code = None
+    reply, notes = raw, ""
Relevance

●●● Strong

Timeouts can be classified as ordinary non-visible responses instead of explicitly untested results.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The exception handler sets only partial output and a null exit code, while _ask explicitly accepts
null exit codes unless selected failure text appears in notes. An empty accepted response reaches
judge_single, which returns not_visible when it contains neither a token nor a qualifying skill
name.

docs/probes/2026-09-16-skills-discovery/lib/print_driver.py[24-35]
docs/probes/2026-09-16-skills-discovery/probe.py[210-222]
docs/probes/2026-09-16-skills-discovery/lib/verdict.py[14-20]

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

## Issue description
Print-mode timeouts are returned without a failure marker, allowing empty or partial timeout output to be judged as a valid skill-discovery response.

## Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/print_driver.py[24-35]
- docs/probes/2026-09-16-skills-discovery/probe.py[210-222]

## Recommended Fix
Record an explicit timeout note and failure status in `AskResult`, and make `_ask` reject every timed-out result regardless of partial output. The guarded scenario path should then record the run as `untested` with the timeout reason.

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


2. Print timeouts can hang forever ✓ Resolved 🐞 Bug ☼ Reliability
Description
print_ask launches vendors without a new process session or process-group cleanup. When a vendor
re-execs or leaves a descendant holding stdout, subprocess.run can remain blocked after killing
the direct child and the descendant survives the probe.
Code

docs/probes/2026-09-16-skills-discovery/lib/print_driver.py[R18-21]

+            proc = subprocess.run(
+                argv, cwd=str(cwd), env=env, input=stdin_text, capture_output=False,
+                stdout=subprocess.PIPE, stderr=err, text=True, timeout=timeout,
+                stdin=None if stdin_text is not None else subprocess.DEVNULL,
Relevance

●●● Strong

Missing process-group cleanup can leave descendants holding pipes and block timeout handling
indefinitely.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The print driver starts the vendor without start_new_session and performs no group cleanup,
although the probe already provides a process-group kill helper. Every print adapter uses this
driver, and the probe's own findings document that Gemini re-execs and can leave a grandchild
holding stdout.

docs/probes/2026-09-16-skills-discovery/lib/print_driver.py[18-26]
docs/probes/2026-09-16-skills-discovery/lib/procs.py[7-12]
docs/probes/2026-09-16-skills-discovery/findings.md[208-209]

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

## Issue description
Print-mode vendors are not placed in an isolated process group, so timeout handling can leave descendants alive and block while they retain inherited pipes.

## Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/print_driver.py[18-26]
- docs/probes/2026-09-16-skills-discovery/lib/procs.py[7-12]

## Recommended Fix
Replace `subprocess.run` with explicit `Popen` lifecycle management using `start_new_session=True`. On timeout, terminate the process group with the existing helper, collect bounded partial output, and ensure all descriptors and descendants are cleaned up before returning.

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


3. Shared roots appear vendor-isolated ✓ Resolved 🐞 Bug ≡ Correctness
Description
summarise counts only visible S4 verdicts when determining which entries consume each root,
excluding the explicit leaked consumption verdict. When one entry consumes another vendor's
undocumented root, the report can still recommend that root as isolated to its nominal vendor.
Code

docs/probes/2026-09-16-skills-discovery/report.py[R39-41]

+    for r in rows:
+        if r["scenario"] == "S4" and r["verdict"] in VISIBLE and r["root"]:
+            consumed_by[r["root"]].add(r["entry"])
Relevance

●●● Strong

Excluding leaked verdicts hides actual cross-vendor root consumption from isolation reporting.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The report adds consumers only for verdicts in VISIBLE and marks a root isolated when that set
contains one entry. The scenario evaluator deliberately returns leaked when an undocumented root's
token is found, proving that such rows represent actual consumers omitted by the calculation.

docs/probes/2026-09-16-skills-discovery/report.py[38-55]
docs/probes/2026-09-16-skills-discovery/probe.py[316-320]
docs/probes/2026-09-16-skills-discovery/lib/verdict.py[29-32]

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

## Issue description
Vendor-isolation reporting ignores entries that consume an undocumented root because those measurements use the `leaked` verdict rather than a visible verdict.

## Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/report.py[38-55]
- docs/probes/2026-09-16-skills-discovery/probe.py[316-320]

## Recommended Fix
Include `leaked` S4 rows in `consumed_by` while continuing to distinguish them in the per-entry roots display. Add a report test where one vendor has visible consumption and a second vendor has leaked consumption of the same root, and assert that the root is not isolated.

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


View high (1)
4. Tool output can fake discovery 🐞 Bug ≡ Correctness
Description
parse_reply scans the complete raw event stream whenever reply extraction is empty, so a skill
token echoed by a file-read or tool-result frame becomes a reported token. A failed or incomplete
Codex reply after reading SKILL.md can consequently be marked visible, and the same raw token can
abort S0 as a prompt-design failure.
Code

docs/probes/2026-09-16-skills-discovery/lib/probe_skill.py[R86-95]

+def parse_reply(reply_text: str, raw: str, name: str | None = None) -> Reply:
+    # The reply decides; the raw event log is consulted only when no reply text was extracted,
+    # because a tool-call frame that echoes SKILL.md would otherwise count as a loaded skill.
+    found = TOKEN_RE.findall(reply_text) if reply_text.strip() else TOKEN_RE.findall(raw)
+    # A reply that lists some other probe skill has not named this one.
+    named = (name in reply_text) if name else NAME_RE.search(reply_text) is not None
+    return Reply(
+        tokens=frozenset(found),
+        skill_named=named,
+        no_skill=NO_SKILL in reply_text,
Relevance

●●● Strong

Raw tool events can incorrectly supply discovery tokens when no model reply was extracted.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The parser expressly substitutes raw for an empty reply and accepts every matching token. Codex
retains its complete JSON event stream when no agent message was extracted, including completed tool
items, while S0 passes this parsed result directly to the exception-raising control judge.

docs/probes/2026-09-16-skills-discovery/lib/probe_skill.py[86-95]
docs/probes/2026-09-16-skills-discovery/harness/codex.py[53-69]
docs/probes/2026-09-16-skills-discovery/probe.py[237-248]

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 fallback token parser treats any raw vendor event as model output, including tool results that contain the skill body.

Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/probe_skill.py[86-95]
- docs/probes/2026-09-16-skills-discovery/harness/codex.py[53-69]

Recommended Fix
Remove the unrestricted raw-stream token fallback, or pass a separately extracted agent-message fallback into `parse_reply`. Never scan raw tool-call or tool-result payloads for probe tokens; if no agent reply can be extracted, record the run as untested or not visible according to the driver failure state.

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



Remediation recommended

5. Copilot catalogue is never collected ✓ Resolved 🐞 Bug ≡ Correctness
Description
list_catalogue is nested after the return in the module-level classify_copilot_tools function
instead of being defined as a method on CopilotAdapter. When free_phase requests the catalogue,
method resolution reaches the inherited no-op Adapter.list_catalogue, so it records `catalogue:
null` even when the installed Copilot CLI can list skills.
Code

docs/probes/2026-09-16-skills-discovery/harness/copilot.py[R124-127]

+    def list_catalogue(self, sb: Sandbox) -> str | None:
+        out = subprocess.run([self.binary_path() or self.binary, "skill", "list", "--json"], cwd=str(sb.repo),
+                             env=sb.env, capture_output=True, text=True, timeout=120)
+        return out.stdout
Relevance

●●● Strong

Clear indentation bug prevents the intended adapter override and makes catalogue collection a no-op.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The declaration remains lexically indented inside classify_copilot_tools, after that helper has
already returned, so it is not available through a CopilotAdapter instance. Because the base
adapter implementation returns None and the free phase stores the result of that virtual method
call, catalogue collection produces a null value rather than invoking the implemented Copilot
command.

docs/probes/2026-09-16-skills-discovery/harness/copilot.py[111-127]
docs/probes/2026-09-16-skills-discovery/harness/base.py[76-77]
docs/probes/2026-09-16-skills-discovery/probe.py[414-425]
docs/probes/2026-09-16-skills-discovery/probe.py[413-420]

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

## Issue description
`list_catalogue` is declared inside `classify_copilot_tools` and is therefore unreachable through `CopilotAdapter`, causing Copilot to inherit the base implementation that returns `None`.

## Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/harness/copilot.py[13-72]
- docs/probes/2026-09-16-skills-discovery/harness/copilot.py[111-127]

## Recommended Fix
Dedent `list_catalogue` and place it at class indentation inside `CopilotAdapter`, alongside `ask`. Preserve its invocation of `copilot skill list --json` with the sandbox repository and environment, and add a test asserting that the free phase invokes this Copilot-specific implementation.

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


6. The pull request leaves its issue open 📘 Rule violation § Compliance
Description
The PR reference line uses Refs #961 — AI-2829 rather than one of the required GitHub closing
keywords. GitHub will therefore treat #961 only as a reference when this branch merges, even
though both issue identifiers appear together.
Code

docs/superpowers/plans/2026-09-16-ai2829-skills-discovery-probes.md[3620]

+Open the PR with `gh pr create` following `.github/PULL_REQUEST_TEMPLATE.md`. Title: `Probe repo-local skill discovery and startup ordering across harnesses`. The reference line carries `Refs #961` (not a closing keyword: pass 2 closes it) and `AI-2829`. End the description with `🤖 Generated with [Claude Code](https://claude.com/claude-code)`.
Relevance

●●● Strong

Recent compliance guidance requires a closing keyword plus Linear key; this reference explicitly
uses non-closing Refs.

PR-#659

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 2897991 requires the PR description's reference line to contain exactly one GitHub
closing keyword and a Linear issue key on the same line. The added delivery plan explicitly
prescribes Refs #961 and notes that it is not a closing keyword, matching the non-closing
reference used in the supplied PR description.

Rule 2897991: PR description must contain both GitHub and Linear issue references; PR title must not contain issue IDs
docs/superpowers/plans/2026-09-16-ai2829-skills-discovery-probes.md[3620-3620]

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 begins with `Refs #961 — AI-2829`, but the compliance requirement calls for one GitHub closing keyword and the Linear identifier on the same reference line.

## Fix Focus Areas
- docs/superpowers/plans/2026-09-16-ai2829-skills-discovery-probes.md[3620-3620]

## Recommended Fix
Change the planned and actual PR reference line to use a supported closing keyword, such as `Closes #961 AI-2829`, while keeping both identifiers on that single line.

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


7. Results merge across operating systems ✓ Resolved 🐞 Bug ≡ Correctness
Description
emit_matrix omits each RunRecord.os from its grouping key and derives the emitted OS label
solely from the first group member. Reusing an output directory for otherwise matching runs from
different operating systems combines their verdicts, flakiness, and evidence into one row and
publishes the majority as though every run came from the first platform.
Code

docs/probes/2026-09-16-skills-discovery/lib/recorder.py[R82-89]

+        groups.setdefault((r.entry, r.version, r.mode, r.scenario, r.arm, r.root or "", r.exclusion), []).append(r)
+    rows = []
+    for key in sorted(groups):
+        members = groups[key]
+        verdict, flaky = combine([m.verdict for m in members])
+        first = members[0]
+        rows.append({
+            "entry": first.entry, "harness": first.harness, "version": first.version, "os": first.os,
Relevance

●●● Strong

Omitting OS from grouping merges distinct platform results and mislabels combined evidence.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Each RunRecord captures an os, but the aggregation tuple includes entry, version, mode,
scenario, arm, root, and exclusion while omitting that value. All matching records therefore feed
the same combine operation, after which the emitted row copies members[0].os, concealing any
other platforms and their evidence represented in the group.

docs/probes/2026-09-16-skills-discovery/lib/recorder.py[76-95]
docs/probes/2026-09-16-skills-discovery/lib/recorder.py[13-19]
docs/probes/2026-09-16-skills-discovery/lib/recorder.py[76-93]

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

## Issue description
Matrix emission combines otherwise matching probe runs from different operating systems, including their verdicts, flakiness counts, and evidence, while labeling the result with only the first run's platform.

## Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/recorder.py[76-96]

## Recommended Fix
Add `r.os` to the matrix grouping key so each platform emits an independent row whose verdict, flakiness count, and evidence represent only that operating system. If the matrix is intended to distinguish separate installations that report the same version, also include binary identity in the key. Add a test with otherwise matching runs from two operating systems and assert that two rows with separate evidence lists are emitted.

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


View medium (4)
8. Custom matrix paths crash emission ✓ Resolved 🐞 Bug ≡ Correctness
Description
emit_matrix requires every run path to be beneath target.parent by calling Path.relative_to
without a fallback. When the independently configurable --outdir and --matrix locations are
siblings or otherwise unrelated, --emit raises ValueError before writing the requested report.
Code

docs/probes/2026-09-16-skills-discovery/lib/recorder.py[93]

+            "evidence": [str(m._path.relative_to(target.parent)) for m in members],  # type: ignore[attr-defined]
Relevance

●●● Strong

Independent output and matrix paths can make relative_to raise before report emission.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Run records are loaded from the user-selected output directory while the CLI accepts the matrix
target independently and imposes no nesting constraint between the two paths. Evidence serialization
nevertheless calls relative_to(target.parent), and Path.relative_to raises when a run path is
outside that parent, proving that this valid option combination cannot complete.

docs/probes/2026-09-16-skills-discovery/probe.py[443-454]
docs/probes/2026-09-16-skills-discovery/lib/recorder.py[66-73]
docs/probes/2026-09-16-skills-discovery/lib/recorder.py[76-96]
docs/probes/2026-09-16-skills-discovery/lib/recorder.py[89-96]
docs/probes/2026-09-16-skills-discovery/probe.py[443-453]

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

## Issue description
Matrix evidence path generation assumes that every run file is nested beneath the matrix destination, even though the CLI accepts `--outdir` and `--matrix` independently. As a result, emission fails when those locations are siblings or otherwise unrelated.

## Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/recorder.py[76-96]
- docs/probes/2026-09-16-skills-discovery/probe.py[443-454]

## Recommended Fix
Preserve the current evidence paths relative to `target.parent` for the default nested layout, but support unrelated directories by using a relative-path operation such as `os.path.relpath` or by falling back to an absolute path when no safe relative representation is available. A path relative to `outdir` may also be used as the fallback if it remains resolvable by the report consumer. Add a test that emits a matrix when the output and matrix destinations are sibling or unrelated temporary directories.

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


9. Minimum versions are reported incorrectly ✓ Resolved 🐞 Bug ≡ Correctness
Description
summarise lexicographically sorts every observed version and writes the entire joined list into
the singular minimum-version field. With multiple measurements, values such as 10.0 and 2.0
produce 10.0, 2.0 instead of the earliest version for which the finding is claimed.
Code

docs/probes/2026-09-16-skills-discovery/report.py[R67-69]

+            "Reload path": "; ".join(reload) if reload else ("none" if status == "measured" else "—"),
+            "GUI status": GUI_STATUS.get(entry, "n/a"),
+            "Minimum version": ", ".join(versions) if status == "measured" else "—",
Relevance

●●● Strong

The singular minimum-version field incorrectly emits every observed version rather than the earliest
one.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The implementation sorts raw version strings and reuses the full joined list for both tested
versions and minimum version. The design defines this column as the earliest version for which the
finding is claimed, so the two fields require different aggregation.

docs/probes/2026-09-16-skills-discovery/report.py[43-69]
docs/superpowers/specs/2026-09-16-skills-discovery-probes-design.md[270-270]

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 minimum-version report field contains every version in string order rather than the earliest applicable measured version.

## Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/report.py[43-69]
- docs/superpowers/specs/2026-09-16-skills-discovery-probes-design.md[270-270]

## Recommended Fix
Select one earliest version from the rows that support the reported finding, comparing numeric version components rather than raw strings. Keep all tested versions in `Version tested`, and add multi-version tests covering values such as `2.0` and `10.0` plus untested versions.

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


10. Completed daemon turns wait unnecessarily 🐞 Bug ☼ Reliability
Description
_Rpc.request absorbs notifications received before the matching turn/start response, but
wait_notification never checks the already absorbed notification list. When turn/completed
arrives before that response, the app-server driver waits the full turn timeout and records
turn=None although completion and reply events were already received.
Code

docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py[R37-46]

+    def wait_notification(self, method: str, timeout: float) -> dict | None:
+        deadline = time.time() + timeout
+        while time.time() < deadline:
+            msg = self.child.recv(max(0.1, deadline - time.time()))
+            if msg is None:
+                return None
+            self._absorb(msg)
+            if msg.get("method") == method:
+                return msg
+        return None
Relevance

●●● Strong

Buffered notifications are demonstrably ignored, causing completed turns to wait until timeout.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Nonmatching notifications are appended during request, including while awaiting turn/start; the
later wait loop reads only future queue messages. Reply extraction separately reads the buffered
collection, demonstrating that a completed turn's events can exist while the synchronization wait
misses its completion notification.

docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py[17-46]
docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py[97-126]

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 app-server driver loses completion notifications for synchronization when they were buffered while waiting for a JSON-RPC response.

Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py[17-46]
- docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py[97-117]

Recommended Fix
Make `wait_notification` search buffered notifications before reading new frames, consuming or marking the matched notification as appropriate. Continue to collect item notifications for reply extraction without waiting for a completion that was already received.

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


11. Selected scenarios bypass the baseline 🐞 Bug ≡ Correctness
Description
run_scenario treats an absent S1 result as a successful native-root control through
self.s1_ok.get(mode, True). Invoking the advertised --scenario S2, S3, or S4 option without
S1, including in a later invocation with existing S1 records, spends turns and emits conclusions
despite never establishing that the harness reads its native root.
Code

docs/probes/2026-09-16-skills-discovery/probe.py[R339-347]

+    def run_scenario(self, mode: str, scenario: str, arms: list[str] | None = None) -> list[RunRecord]:
+        gated = scenario not in ("S0", "S1") and not self.s1_ok.get(mode, True)
+        native = self.adapter.native_root
+        out: list[RunRecord] = []
+        if scenario == "S0":
+            out += self.run_arm(lambda: self.arm_s0(mode), mode, "S0", "S0/none", None, "none")
+        elif scenario == "S1":
+            recs = self.run_arm(lambda: self.arm_s1(mode), mode, "S1", "S1/native", native, "none")
+            self.s1_ok[mode] = combine([r.verdict for r in recs])[0] == "visible_first_turn"
Relevance

●●● Strong

Missing S1 state defaults to success, allowing dependent scenarios without baseline validation.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Only the S1 branch populates s1_ok, but the gate defaults a missing entry to true. The
command-line interface permits selecting arbitrary scenarios and does not insert S1 before the
selected scenario list.

docs/probes/2026-09-16-skills-discovery/probe.py[339-381]
docs/probes/2026-09-16-skills-discovery/probe.py[436-478]

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

Issue description
Targeted later scenarios default to passing the required S1 native-root control when S1 was not run in this `Runner` instance.

Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/probe.py[160-169]
- docs/probes/2026-09-16-skills-discovery/probe.py[339-381]
- docs/probes/2026-09-16-skills-discovery/probe.py[436-478]

Recommended Fix
Before S2, S3, or S4, load and evaluate settled S1 records for the requested mode, or run S1 automatically when no valid control exists. Block later scenarios when that control is absent, untested, or not visible.

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


Grey Divider

Context sources
✅ Compliance rules (platform): 64 rules
✅ Cross-repo context — repo relationships
  Explored: repo: kurrent-io/cloud-meta (sha: 15782900)
Review mode: 🧠 Deep: This adds substantial, independently implemented probe logic across many harnesses, drivers, isolation paths, and self-tests, making multiple subtle defects easy to miss in a single review pass.

Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

/usr/bin/git -C <worktree> push https://github.com/kurrent-io/kcap-cli.git alexeyzimarev/ai-2829-verify-repo-local-skill-discovery-and-startup-ordering
```

Open the PR with `gh pr create` following `.github/PULL_REQUEST_TEMPLATE.md`. Title: `Probe repo-local skill discovery and startup ordering across harnesses`. The reference line carries `Refs #961` (not a closing keyword: pass 2 closes it) and `AI-2829`. End the description with `🤖 Generated with [Claude Code](https://claude.com/claude-code)`.

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

5. The pull request leaves its issue open 📘 Rule violation § Compliance

The PR reference line uses Refs #961 — AI-2829 rather than one of the required GitHub closing
keywords. GitHub will therefore treat #961 only as a reference when this branch merges, even
though both issue identifiers appear together.
Agent Prompt
## Issue description
The PR description begins with `Refs #961 — AI-2829`, but the compliance requirement calls for one GitHub closing keyword and the Linear identifier on the same reference line.

## Fix Focus Areas
- docs/superpowers/plans/2026-09-16-ai2829-skills-discovery-probes.md[3620-3620]

## Recommended Fix
Change the planned and actual PR reference line to use a supported closing keyword, such as `Closes #961 AI-2829`, while keeping both identifiers on that single line.

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

Comment thread docs/probes/2026-09-16-skills-discovery/lib/print_driver.py Outdated
Comment thread docs/probes/2026-09-16-skills-discovery/lib/print_driver.py Outdated
Comment thread docs/probes/2026-09-16-skills-discovery/harness/copilot.py Outdated
Comment thread docs/probes/2026-09-16-skills-discovery/report.py Outdated
Comment thread docs/probes/2026-09-16-skills-discovery/lib/recorder.py Outdated
Comment thread docs/probes/2026-09-16-skills-discovery/lib/recorder.py Outdated
Comment on lines +86 to +95
def parse_reply(reply_text: str, raw: str, name: str | None = None) -> Reply:
# The reply decides; the raw event log is consulted only when no reply text was extracted,
# because a tool-call frame that echoes SKILL.md would otherwise count as a loaded skill.
found = TOKEN_RE.findall(reply_text) if reply_text.strip() else TOKEN_RE.findall(raw)
# A reply that lists some other probe skill has not named this one.
named = (name in reply_text) if name else NAME_RE.search(reply_text) is not None
return Reply(
tokens=frozenset(found),
skill_named=named,
no_skill=NO_SKILL in reply_text,

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

4. Tool output can fake discovery 🐞 Bug ≡ Correctness

parse_reply scans the complete raw event stream whenever reply extraction is empty, so a skill
token echoed by a file-read or tool-result frame becomes a reported token. A failed or incomplete
Codex reply after reading SKILL.md can consequently be marked visible, and the same raw token can
abort S0 as a prompt-design failure.
Agent Prompt
Issue description
The fallback token parser treats any raw vendor event as model output, including tool results that contain the skill body.

Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/probe_skill.py[86-95]
- docs/probes/2026-09-16-skills-discovery/harness/codex.py[53-69]

Recommended Fix
Remove the unrestricted raw-stream token fallback, or pass a separately extracted agent-message fallback into `parse_reply`. Never scan raw tool-call or tool-result payloads for probe tokens; if no agent reply can be extracted, record the run as untested or not visible according to the driver failure state.

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

Comment on lines +339 to +347
def run_scenario(self, mode: str, scenario: str, arms: list[str] | None = None) -> list[RunRecord]:
gated = scenario not in ("S0", "S1") and not self.s1_ok.get(mode, True)
native = self.adapter.native_root
out: list[RunRecord] = []
if scenario == "S0":
out += self.run_arm(lambda: self.arm_s0(mode), mode, "S0", "S0/none", None, "none")
elif scenario == "S1":
recs = self.run_arm(lambda: self.arm_s1(mode), mode, "S1", "S1/native", native, "none")
self.s1_ok[mode] = combine([r.verdict for r in recs])[0] == "visible_first_turn"

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

10. Selected scenarios bypass the baseline 🐞 Bug ≡ Correctness

run_scenario treats an absent S1 result as a successful native-root control through
self.s1_ok.get(mode, True). Invoking the advertised --scenario S2, S3, or S4 option without
S1, including in a later invocation with existing S1 records, spends turns and emits conclusions
despite never establishing that the harness reads its native root.
Agent Prompt
Issue description
Targeted later scenarios default to passing the required S1 native-root control when S1 was not run in this `Runner` instance.

Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/probe.py[160-169]
- docs/probes/2026-09-16-skills-discovery/probe.py[339-381]
- docs/probes/2026-09-16-skills-discovery/probe.py[436-478]

Recommended Fix
Before S2, S3, or S4, load and evaluate settled S1 records for the requested mode, or run S1 automatically when no valid control exists. Block later scenarios when that control is absent, untested, or not visible.

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

Comment on lines +37 to +46
def wait_notification(self, method: str, timeout: float) -> dict | None:
deadline = time.time() + timeout
while time.time() < deadline:
msg = self.child.recv(max(0.1, deadline - time.time()))
if msg is None:
return None
self._absorb(msg)
if msg.get("method") == method:
return msg
return None

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

11. Completed daemon turns wait unnecessarily 🐞 Bug ☼ Reliability

_Rpc.request absorbs notifications received before the matching turn/start response, but
wait_notification never checks the already absorbed notification list. When turn/completed
arrives before that response, the app-server driver waits the full turn timeout and records
turn=None although completion and reply events were already received.
Agent Prompt
Issue description
The app-server driver loses completion notifications for synchronization when they were buffered while waiting for a JSON-RPC response.

Fix Focus Areas
- docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py[17-46]
- docs/probes/2026-09-16-skills-discovery/lib/appserver_driver.py[97-117]

Recommended Fix
Make `wait_notification` search buffered notifications before reading new frames, consuming or marking the matched notification as appropriate. Continue to collect item notifications for reply extraction without waiting for a completion that was already received.

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

@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: 2618e435fa

ℹ️ 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".

# Backgrounded commands get /dev/null on fd 0 unless explicitly redirected, so the real
# stdin is saved to fd 3 first and handed to the background reader from there. A vendor
# that hands the hook no stdin at all must not abort the script under set -e.
"{ exec 3<&0; } 2>/dev/null || exec 3</dev/null\n"

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 Handle closed stdin without a failing exec redirection

When a vendor launches the hook with fd 0 closed, /bin/sh treats the failed redirection on the exec special builtin as fatal and exits with status 2 before the || fallback runs. This makes test_script_tolerates_a_closed_stdin fail and can cause vendors to reject an otherwise completed startup hook; duplicate stdin to fd 3 using a construct that cannot terminate the current shell on redirection failure.

Useful? React with 👍 / 👎.

plugin = sb.root / "plugin"
(plugin / ".cursor-plugin").mkdir(parents=True, exist_ok=True)
(plugin / ".cursor-plugin" / "plugin.json").write_text(json.dumps({"name": "kcap-probe", "version": "1.0.0"}))
target = plugin / "skills" / skill_file.parent.name / "SKILL.md"

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 Preserve the Cursor registration artifact the runner verifies

For the Cursor registration arm, skill_file points into the repository's native root, but this replaces it with a plugin-local target and never creates the original path. After the turn, Runner.arm_s2 checks target.exists() for that original skill_file and forcibly changes the verdict to untested, so Cursor registration can never be measured successfully even if pluginPaths loads the generated skill.

Useful? React with 👍 / 👎.

Comment on lines +40 to +41
if r["scenario"] == "S4" and r["verdict"] in VISIBLE and r["root"]:
consumed_by[r["root"]].add(r["entry"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count leaked loads when determining vendor isolation

When an entry loads a root that it does not document, S4 records the verdict as leaked, but this index counts only the two visible verdicts. Consequently, if entry X legitimately consumes a root and entry Y leaks that same root, the report still labels it a vendor-isolated destination for X, producing an incorrect capability matrix precisely in the cross-vendor collision case that S4 is intended to detect.

Useful? React with 👍 / 👎.

except Exception as ex: # noqa: BLE001
aborted += 1
print(f"{name}: aborted: {ex!r}")
return 1 if aborted and not completed else 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return failure when any selected harness aborts

In a multi-harness sweep, any successful entry increments completed, causing the command to return 0 even when another entry raises an exception or fails its negative control. Automation therefore treats a partial, aborted experiment as successful and may emit incomplete results; the exit status should remain nonzero whenever aborted is nonzero.

Useful? React with 👍 / 👎.

Comment on lines +124 to +127
def list_catalogue(self, sb: Sandbox) -> str | None:
out = subprocess.run([self.binary_path() or self.binary, "skill", "list", "--json"], cwd=str(sb.repo),
env=sb.env, capture_output=True, text=True, timeout=120)
return out.stdout

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move list_catalogue onto CopilotAdapter

This definition is indented inside classify_copilot_tools after its unconditional return, rather than being a method of CopilotAdapter. As a result, CopilotAdapter inherits the base no-op implementation and the free phase never runs copilot skill list --json, leaving the claimed catalogue corroboration absent from newly generated evidence.

Useful? React with 👍 / 👎.

alexeyzimarev and others added 11 commits September 16, 2026 16:21
…961)

Their keyring logins are not visible from a private HOME, and a signed-out agy starts a browser sign-in on every launch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The json and text formats print nothing once the stream error ends a tool-using turn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…turn (#961)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ols (#961)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A scenario an entry never ran renders as not run rather than none, so a blocked harness cannot read as a measured negative.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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