Skip to content

feat(repo): Codex and Copilot CLI harness profiles, native review mode - #116

Open
napetrov wants to merge 13 commits into
intel:mainfrom
napetrov:feat/harness-profiles-codex-copilot
Open

napetrov wants to merge 13 commits into
intel:mainfrom
napetrov:feat/harness-profiles-codex-copilot

Conversation

@napetrov

Copy link
Copy Markdown
Contributor

Summary

repo run and repo reach had one built-in harness, Claude Code. This adds the Codex CLI and the Copilot CLI, including each CLI's own review mode, so the same task can be compared across guidance arms × harness × model.

 dokimi/repos/
 ├── profiles/
 │   ├── claude-code.yaml
+│   ├── codex.yaml            # codex exec --json … -- {prompt}
+│   ├── codex-review.yaml     # codex exec review --commit HEAD (native prompt)
+│   ├── copilot.yaml          # copilot -p {prompt} --output-format json
+│   └── copilot-review.yaml   # copilot -p "/review {prompt}"
 ├── events.py                 # trace: + codex-json, copilot-json
+├── cli_traces.py             # the two JSON converters -> events.jsonl
+├── answers.py                # answer_format: codex-review -> findings block
 ├── runner.py                 # answer_format applied; bin_companions mounted
 └── reach.py                  # refuses native_prompt; now writes config_files

harness_profile.v1 is widened in place with three optional keys:

native_prompt: true          # the harness's own prompt; invoke has no {prompt}
answer_format: codex-review  # "- [P1] title — path:12-14" -> ```json [{file, line, severity, rule, message}]
bin_companions: [codex-code-mode-host]   # mounted beside /eval/harness/codex
repo run cell (codex-review)
  agent container: /eval/harness/codex + codex-code-mode-host
    codex exec review --commit HEAD --json      # task prompt not passed
  convert("codex-json") -> events.jsonl
  convert_answer("codex-review") -> answer.txt  # text kept, ```json block appended
  grade                                         # review grader unchanged

Things that only show up when running the real CLIs:

  • codex review --commit refuses a prompt.
  • Codex 0.160 runs every tool call through codex-code-mode-host beside its binary, so a single-file mount leaves it with no tools.
  • An empty Copilot --available-tools list leaves every tool on, so none names a tool that doesn't exist.
  • npm installs both CLIs as Node launchers; --bin has to name the native binary.

Not here: hosted PR reviewers (Copilot code review, @codex review, CodeRabbit). They are not CLIs and need a separate PR-replay adapter.

Evidence

  • Before: repo run --harness codex fails with no harness profile 'codex' (built-in: claude-code). A profile with a Codex provider in config_files probed via repo reach hit wss://api.openai.com with a 401, because reach never wrote the file.
    After: live runs on oneDAL review_seeded_fc (fork dev/agent-evals c4e2667f5, image agent-benchmark/onedal:a28e8a6c33a7, gpt-6-luna through a LiteLLM proxy, guidance:raw, 1 rep):

    harness recall events notes
    codex 0.50 12 command, 9 read
    codex-review 0.375 7 command, 8 read same recall as an out-of-tree harness on 10-04
    copilot-review 0.625 5 command, 14 read, 13 tool 11 turns

    The first codex-review run, without bin_companions, answered "the repository command tool failed to start" and got recall 0.0.

  • Live repo reach on oneDAL main 7f6d9f86 (3 scenarios, gpt-6-luna):

                           cwd=.  cwd=cpp/daal  read=cpp/daal
    codex   AGENTS.md        L       L             L
            cpp/AGENTS.md    .       L             .
            cpp/daal/AGENTS  .       L             .
    copilot AGENTS.md        L       L             .   <- model refused to list tokens (prose answer)
            cpp/AGENTS.md    .       L             .
            cpp/daal/AGENTS  .       L             .
    

    Both CLIs load AGENTS.md from the git root down to cwd, and reading a file in a subtree does not pull in that subtree's AGENTS.md. In read= Codex skipped the read, because the reach ask says "use the Read tool" and Codex has no tool by that name. Rewording the ask is left for a follow-up: it would change Claude's recorded ask too.

  • Tests: tests/test_repo_cli_harnesses.py (29 tests) covers:

    • argv for each profile;
    • BYOK env pass-through;
    • refused native profiles;
    • shell_reads, including pipes, redirects and sed -i;
    • both converters, on shapes taken from real traces;
    • codex_review_findings;
    • a repo run cell that grades a native review answer;
    • bin_companions mounted, or a stop before any agent runs.

    test_repo_reach.py adds a test that a profile's config_files reach the probe.

  • make ci-local: every row passes except test. There, every PR-touched test passes, but 1–3 unrelated tests fail at random, a different one each run: test_process Popen ResourceWarning, test_container_agent "AF_UNIX path too long". This happens under 224 xdist workers on a shared box. pytest -n 32 gives 5208 passed, 2 skipped. Those two files alone: 149 passed.

Merge Danger

Door: two-way

New profiles and optional schema keys only. Profiles that already exist are left as they were. schema_compat passes against the base.

Blast Radius: repo reach

Every reach run now writes the profile's config_files. For claude-code that adds the no-background hook settings.json to each scenario's config dir. Reach scenarios run with tools none/read, so the Bash hook never fires. Only repo run / repo reach read the new keys.

🤖 Generated with Claude Code

Add built-in harness_profile.v1 files codex, codex-review, copilot and
copilot-review, so repo run and repo reach can compare guidance arms under
codex exec, codex exec review --commit HEAD, copilot -p and Copilot's
/review agent, not only Claude Code.

- trace: codex-json and copilot-json convert the CLIs' JSON event streams
  to events.jsonl (cli_traces.py); Codex read events come from shell
  commands that only print files.
- native_prompt: a profile may run the harness's own prompt; reach refuses it.
- answer_format: codex-review appends a findings block parsed from Codex's
  review text (answers.py), so review graders read it unchanged.
- bin_companions: helper files mounted beside the executable (Codex's
  codex-code-mode-host, without which no tool call runs in the container).
- repo reach now writes the profile's config_files, as repo run does.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@codecov-commenter

codecov-commenter commented Oct 19, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 85.75342% with 52 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.21%. Comparing base (6e695ce) to head (b4457f9).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
dokimi/dokimi/repos/cli_traces.py 82.43% 28 Missing and 24 partials ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #116      +/-   ##
==========================================
- Coverage   90.24%   90.21%   -0.04%     
==========================================
  Files         206      208       +2     
  Lines       24289    24709     +420     
  Branches     3906     4015     +109     
==========================================
+ Hits        21920    22290     +370     
- Misses       1703     1730      +27     
- Partials      666      689      +23     
Files with missing lines Coverage Δ
dokimi/dokimi/repos/answers.py 100.00% <100.00%> (ø)
dokimi/dokimi/repos/events.py 91.27% <100.00%> (+0.17%) ⬆️
dokimi/dokimi/repos/harness_profile.py 94.16% <100.00%> (+0.36%) ⬆️
dokimi/dokimi/repos/reach.py 96.60% <100.00%> (+0.05%) ⬆️
dokimi/dokimi/repos/runner.py 93.30% <100.00%> (+0.32%) ⬆️
dokimi/dokimi/repos/cli_traces.py 82.43% <82.43%> (ø)

... and 13 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI 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.

🟡 Changes recommended

Trace conversion can distort evaluation results, and reach configuration produces invalid JSON on Windows.

4 open issues
What changed in this PR

Adds Codex and Copilot CLI harnesses so repository evaluations can compare guidance across agents, including native review modes.

Changes:

  • Adds four harness profiles and optional profile settings.
  • Converts CLI traces and native review answers into evaluation artifacts.
  • Applies profile configuration during reach probes, with tests and documentation.
File Description
dokimi/​tests/​test_repo_reach.py Tests reach configuration generation.
dokimi/​tests/​test_repo_cli_harnesses.py Tests profiles, converters, and runner integration.
dokimi/​dokimi/​schemas/​harness_profile.v1.json Adds optional harness capabilities.
dokimi/​dokimi/​repos/​runner.py Mounts companion binaries and converts answers.
dokimi/​dokimi/​repos/​reach.py Writes configuration and rejects native prompts.
dokimi/​dokimi/​repos/​profiles/​copilot.yaml Adds Copilot execution profile.
dokimi/​dokimi/​repos/​profiles/​copilot-review.yaml Adds Copilot review profile.
dokimi/​dokimi/​repos/​profiles/​codex.yaml Adds Codex execution profile.
dokimi/​dokimi/​repos/​profiles/​codex-review.yaml Adds Codex native review profile.
dokimi/​dokimi/​repos/​harness_profile.py Parses and validates new settings.
dokimi/​dokimi/​repos/​events.py Dispatches new trace formats.
dokimi/​dokimi/​repos/​cli_traces.py Normalizes Codex and Copilot events.
dokimi/​dokimi/​repos/​answers.py Converts native review findings.
dokimi/​docs/​repo-evaluation-contract.md Documents profiles and evaluation behavior.
dokimi/​docs/​architecture.md Describes new modules and harnesses.
dokimi/​changelog.d/​20261019_050000_nikolay.a.petrov_codex_copilot_profiles.md Records harness additions and reach fix.

Review effort: Balanced

Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py
Copilot AI balanced review requested due to automatic review settings October 19, 2026 08:02
…d, cd commands, and incomplete commands

On Windows, str(Path) contains backslashes that produce invalid JSON escape
sequences when inserted into configuration files. Use Path.as_posix() for both
config_dir and support_dir to ensure forward slashes.

shell_reads() now splits only on unquoted shell operators (&&, ||, ;, newline),
preserving quoted arguments like sed -n '1p;2p'. Previously, it split all
semicolons regardless of quoting context, breaking multi-range sed commands.

Track directory changes from cd commands in shell scripts so subsequent file
reads are resolved to the correct path. For example, 'cd sub && cat AGENTS.md'
now correctly reports reading 'sub/AGENTS.md'.

Preserve incomplete commands (item.started without item.completed) in Codex
JSON traces. When an agent times out mid-command, record the command with
rc: null per contract §4.1, instead of losing it entirely. Correlate started
and completed items by command text to update rc on completion.

Add regression tests for all three fixes.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI 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.

Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/tests/test_repo_reach.py Outdated
Copilot AI balanced review requested due to automatic review settings October 19, 2026 09:11
Create tests/__init__.py and normalize test module imports to use explicit
'tests.' prefix. This resolves the mypy error where artifact_fixtures was
found under two module names. All imports of test fixtures now consistently
use the 'tests.' prefix (repo_eval_fixture, artifact_fixtures, test_process).

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Fix remaining imports from conftest, test_novelty_analyze, and test_repo_reach
to use explicit 'tests.' prefix for consistency now that tests is a proper package.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Fix remaining import from test_novelty_gate in test_novelty_items.py
to use explicit 'tests.' prefix for consistency.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI 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.

🟡 Changes recommended

The test-package conversion leaves sibling imports unresolved, breaking test collection and execution.

6 open issues

Review effort: Balanced

Comment thread dokimi/tests/__init__.py Outdated
Copilot AI balanced review requested due to automatic review settings October 19, 2026 09:18

Copilot AI 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.

Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
This reverts commit 10c94f3.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
… prefix"

This reverts commit 191a7f1.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…licts"

This reverts commit be51812.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 10:28
…ay uv.lock

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI 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.

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review at 27911d6. The profiles, schema widening and mounting logic hold up, but the trace converters have several wrong-result cases, and the Codex findings parser is fragile. Four things are worth fixing before merge; the inline comments have the inputs and outputs.

What I ran, on a Linux review host at 27911d6:

  • make ci-local: 19 passed, 0 failed, 4 skipped (docker/semgrep/jobs that do not run on a PR); changed-line coverage gate passes.
  • Reproductions for the code findings, with no model or network calls.
  • A mutation run over the new tests: 36 of 83 mutants of cli_traces.py pass the suite; two of the severity-mapping mutants in answers.py pass too.

I agree with Copilot that Codex commands are matched by text, not item.id; it still holds (two overlapping make build items give rc [None, 0, 1]).

Checked, no issues

  • Schema: the harness_profile.v1.json change only widens the trace enum and adds optional keys, so no version bump is needed; schema_compat passes against the base; bin_companions names reject .. and /.
  • Docs: flags, keys, section numbers and the profile table match the code; the changelog fragment is present; no tests/__init__.py or uv.lock remains after the reverts.
  • Mounts and reach: a missing companion fails with a clear error, a symlinked --bin finds the companion beside the real file, and repo reach refuses native_prompt profiles before --dry-run.

Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/cli_traces.py Outdated
Comment thread dokimi/dokimi/repos/answers.py Outdated
from .events import _relative

ANSWER_FORMATS = ("codex-review",)
_SEVERITY = {"P0": "blocker", "P1": "major", "P2": "minor", "P3": "minor"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: the severity mapping has no test.

Mutation check: changing "P3": "minor" to "major", and separately changing the .get(..., "minor") default to "major", both pass the full test_repo_cli_harnesses.py + test_repo_reach.py run (101 passed). The contract documents P2/P3 -> minor. Dropping the "\n\n```json" separator to "\n```json" also passes.

Test to add: one answer with P0, P1, P2, P3 and an unknown P9 findings, asserting all five severities and the exact separator before the block.

The cli_traces.py tests have the same shape of gap: 36 of 83 mutants survive, including no || split, no newline split, no double-quote handling, the sed -i guard, the head/tail -n skipping, and token accumulation over more than one turn.completed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3264d74: test_convert_answer_maps_every_severity_and_separates_the_block covers P0-P3 and unknown P9 and the exact \n\n```json separator. For cli_traces I added cases for ||, newline-free & split, double quotes/expansion, sed -i/-e/-E, $ addresses and cwd; head/tail -n skipping and multi-turn token accumulation are still not pinned (not changed here).

Copilot AI balanced review requested due to automatic review settings October 19, 2026 14:06
… error flags

- Use item.id for command correlation (handles overlapping commands with same text)
- Handle cd options (-L, -P, -) and reset cwd after || operator
- Normalize paths with posixpath.normpath to handle . and .. components
- Improve sed option parsing to handle -e, -f, -E, -s and -i correctly
- Fix is_error flag so bare error doesn't clear turn.failed

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…dings parser; portable config path in test

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI 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.

>
> ```bash
> python cli.py repo reach <source> --manifest <path> --model <id> [--harness claude-code]
> python cli.py repo reach <source> --manifest <path> --model <id> [--harness claude-code|codex|copilot|<profile.yaml>]
if raw.get("native_prompt"):
if passes_prompt or raw.get("prompt_suffix"):
raise ProfileError(f"{path}: native_prompt runs the harness's own prompt; invoke cannot pass "
"{prompt} and there is no prompt_suffix to add")
Comment on lines +55 to +56
SUPPORT_HOST = Path(__file__).resolve().parent / "support" # the framework's helper scripts, as repo run mounts them

Comment on lines +350 to +351
raise ContainerError(f"{opts.profile.name} needs {name} beside its executable, and {real.parent} "
f"has none: pass the --bin of a complete install")

Copilot AI 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.

from collections.abc import Callable
from typing import Any

from .events import _relative
parts = shlex.split(m.group(1))
except ValueError:
return command
return parts[0] if len(parts) == 1 else command
Comment on lines 53 to +56
from .source import RepoTree, SourceError

SUPPORT_HOST = Path(__file__).resolve().parent / "support" # the framework's helper scripts, as repo run mounts them

continue
here = _cd(here, words[1:])
continue
if words[0] not in _PRINTERS :
def _run_one(profile: HarnessProfile, scenario: Scenario, model: str, workspace: Path,
config_dir: Path, bin_path: str | None, max_turns: int, budget_usd: float,
timeout_s: int) -> tuple[Transcript | None, dict[str, Any]]:
config_dir.mkdir()
Copilot AI balanced review requested due to automatic review settings October 19, 2026 15:01
The tests/ directory was added as a package (tests/__init__.py) in this PR, but sibling imports from test modules and conftest still use top-level names. Update all imports to use the tests.* namespace to match the package structure and avoid ModuleNotFoundError during test collection.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Line 259 annotates event: dict[str, Any], and line 274 reassigns without annotation. Line 283 should also reassign without annotation to avoid mypy's no-redef error when the function handles both item.started and item.completed paths.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI 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.

🔵 Needs a closer look

Trace and answer conversion defects can distort guidance classifications and review grading.

9 open issues
3 previously missed issues

In code that hasn't changed since last review

Medium severity Preserve blank-line-separated paragraphs in finding messages

dokimi/​dokimi/​repos/​answers.py:47

A blank line between indented paragraphs ends the finding body here, dropping subsequent paragraphs from the JSON message consumed by graders. Continue across blank lines and stop at the next finding or unindented prose, so the full explanation survives conversion. Add a multi-paragraph regression.

Medium severity Reject incomplete findings when marker parsing fails

dokimi/​dokimi/​repos/​answers.py:71

When finding markers cannot be parsed, this warning still allows an empty or incomplete findings array to be appended. The lowercase/hyphen headers in the new test therefore become a zero-finding review, and partial parses silently omit defects. Raise a conversion error instead: run_cell() already catches it and records a failed outcome rather than grading incomplete findings. Update the warning-only test to expect failure and cover partial parses too.

Medium severity Ignore shell comments when parsing commands and operands

dokimi/​dokimi/​repos/​cli_traces.py:192

Ignore shell comments before splitting operators or extracting operands. cat other.txt # AGENTS.md records a read of AGENTS.md, and true # ; cat AGENTS.md records a read from a command the shell never executes. Both converters use these paths, so reach.classify() can misclassify automatically loaded guidance as fetched. Skip unquoted, unescaped comments through the next newline while preserving literal # characters in words; add regressions for both cases.

5 resolved since last review

Review effort: Balanced

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review at b4457f9. The fixes since 27911d6 hold: cd handling, the sed option parsing, item.id correlation, the turn.failed flag and the findings parser all give the right result on the cases from my earlier threads. What is left is the shell-read heuristics at the edges, one place where a failed Codex run becomes a clean review, and the test gaps.

What I ran, on a Linux review host at b4457f9:

  • make ci-local: 19 passed, 0 failed, 4 skipped (docker, semgrep and jobs that do not run on a PR); 5,235 tests passed, 1 skipped; changed-line coverage 92% (365 lines).
  • Reproductions for the code findings, with no model or network calls.
  • A mutation run over cli_traces.py (129 mutants, 65 pass test_repo_cli_harnesses.py) and over runner.py / reach.py.

Findings that do not anchor inline:

  1. Two mutants in the new plumbing pass the whole suite. Replacing real.parent with opts.harness_bin.parent in the companion lookup (runner.py:345), and removing scenario.cwd from the convert() call in reach.py:389, both pass test_repo_cli_harnesses.py, test_repo_reach.py and test_repo_reach_edges.py (138 passed). No test uses a symlinked --bin (the npm layout the PR description names), and no reach test runs a Codex or Copilot trace from a cwd=sub scenario. Add one test for each.

I agree with @copilot-pull-request-reviewer's points on the duplicated SUPPORT_HOST (runner.py:65, reach.py:55) and on the _PRINTERS : spacing.

Still open from my earlier review:

  • dokimi/tests/test_repo_cli_harnesses.py: the head/tail -n skipping and the token accumulation over more than one turn.completed are still not pinned (you said so in the thread). Other cli_traces.py mutants also survive; see the nit and risk comments for the ones that matter.

Checked, no issues

  • cd and ||: cd - && cat X, cd missing || cat X, cd sub && cd inner && cat X and cat ./X give no read, no read, sub/inner/X and X.
  • Codex events: a turn.failed followed by an error keeps is_error True; two overlapping make build items with different ids give two commands with their own exit codes; a start with no completion keeps rc: null.
  • Findings parser: a title with —, a.py:12-14 ranges, a trailing Overall: line and the marker/finding warning behave as the thread asked.
  • Schema and docs: optional keys only, no version bump needed; schema_compat passes; the changelog fragment is present; mkdocs build passes.

Comment on lines +110 to +115
def _computed(part: str) -> bool:
"""A shell expansion outside single quotes (``$x``, backticks) or a pipe or redirect outside any quote."""
bare = re.sub(r"'[^']*'", "''", part)
if re.search(r"[$`]", bare):
return True
return bool(re.search(r"[|<>]", re.sub(r'"[^"]*"', '""', bare)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any redirect drops the whole read, including 2>/dev/null and 2>&1.

shell_reads() at the PR head:

  • cat a 2>&1 -> []
  • cat a 2>/dev/null -> []
  • sed -n '1,5p' a 2>/dev/null -> []

Agents add 2>/dev/null to read commands all the time. _computed() treats every < or > outside quotes as a write, so repo reach records no read and reports the guidance as not fetched. The test at test_repo_cli_harnesses.py:110 pins cat a 2>&1 -> [], so this is locked in.

Suggestion: strip \d*>&\d+, \d*>/dev/null and &>/dev/null before the check, and treat only a redirect with a real target (> f, >> f, < f) as not-a-read. Change the pinned case and add the /dev/null ones.

if op == "||" and moved:
here = None
try:
words = shlex.split(part) if not _computed(part) else None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Line continuations, heredoc bodies and subshells produce reads of files the agent never opened, or lose the real one.

I agree with @copilot-pull-request-reviewer on shell comments; the same split also gets these wrong:

shell_reads() at the PR head:

  • cat a \\\n b -> ['a', '\n', 'b']
  • cat <<'EOF'\ncat secret\nEOF -> ['secret']; cat > f <<'EOF'\nhead other\nEOF -> ['other'] (the body lines are split on newlines and read as commands)
  • (cd x && cat f) -> ['f)']

A bogus operand can match a guidance path by accident, and the continuation case drops the real file.

Suggestion: join \\\n before splitting, skip heredoc bodies up to the delimiter, and return no read for a part that starts with ( or ends with ).

ended = True
elif a in ("-n", "-c") and cmd in ("head", "tail"):
skip = True
return files

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: option values of head/nl are recorded as files.

head --lines 5 a -> ['5', 'a'], head -qn 5 a -> ['5', 'a'] (line 127 skips the value only for the exact -n/-c). nl -w 3 a records 3. The extra operand is a read of a file named 5, which only matters if such a path exists, hence a nit.

Suggestion: skip the value of --lines/--bytes and of short clusters that end in n/c, and of nl -w/-s/-b/-n.

marked = len(_FINDING_MARK.findall(answer))
if marked > len(findings): # format drift must not read as a clean review
log.warning("%s: %d finding marker(s) in the answer but %d parsed", fmt, marked, len(findings))
return f"{answer}\n\n```json\n{json.dumps(findings, indent=1)}\n```\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A failed or empty Codex run is turned into a clean review.

convert_answer("codex-review", "", "/ws") returns '\n\n```json\n[]\n```\n'. For {"type":"turn.failed","message":"x"} the transcript has is_error=True and answer='', and answer.txt still gets the [] block (runner.py:191). The Claude path leaves answer.txt empty in that case. agent.is_error is recorded but does not reach the grader, so a task that scores "no findings" on a clean patch passes with an agent that crashed or timed out.

Suggestion: return answer unchanged when it is empty (or is_error), so a missing review stays a missing review. Add a test with an empty answer.

This branch has not been deployed

No deployments
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.

4 participants