Skip to content

fix(review-pr): make Console-mode merge-base detection cross-shell compatible#162

Merged
derekmisler merged 4 commits into
mainfrom
fix/console-mode-cross-shell-compat
May 4, 2026
Merged

fix(review-pr): make Console-mode merge-base detection cross-shell compatible#162
derekmisler merged 4 commits into
mainfrom
fix/console-mode-cross-shell-compat

Conversation

@docker-agent
Copy link
Copy Markdown
Contributor

Problem

The Console-mode merge-base detection in review-pr/agents/pr-review.yaml (step 1) used a bash $(...||...) chain with 2>/dev/null, [ -z ... ] tests, exit 1, and tee. These constructs are bash-only. On Windows, cagent's shell tool routes through PowerShell 5.1, which cannot parse any of them, causing local runs to fail immediately.

This regression was introduced in PR #150.

Fixes #155

Changes

review-pr/agents/pr-review.yaml (1 file, 35 insertions / 28 deletions):

  1. Added a cross-shell safety clause to the root agent's ## Process preamble (before step 0). It instructs the agent to keep every shell tool call to a single simple command and avoid ||, &&, 2>/dev/null, [ ... ] tests, and heredocs.

  2. Replaced step 1's Console-mode bash chain with prose instructions that:

    • Direct the agent to try each of the 7 candidate refs (upstream/main, upstream/master, origin/HEAD, origin/main, origin/master, main, master) in its own shell tool call, stopping at the first that returns exit 0 with non-empty output.
    • Each call is simply git merge-base <ref> HEAD — no chaining, no 2>/dev/null.
    • On success: run git diff <BASE> HEAD > ./pr-review.diff then git diff --stat <BASE> HEAD (each as its own shell call).
    • On failure (all 7 candidates fail, or BASE equals HEAD): issue a plain echo and stop — no [ -z ... ] test, no exit 1.
  3. Updated step 4's Console output mode sub-bullet to say the diff was written via > instead of tee.

Test Plan

  • POSIX regression: Run docker agent run on macOS/Linux from a fork repo — confirm the agent correctly issues per-candidate git merge-base calls and produces a diff file.
  • Windows repro: Run docker agent run on Windows (PowerShell 5.1) — confirm the agent no longer fails to parse shell syntax and successfully detects the merge base.
  • Hostile cases:
    • Missing remotes (no upstream, no origin): confirm the agent echoes an error message and stops gracefully.
    • Merge-base equals HEAD (user is on the default branch): confirm the agent echoes an explanatory message and stops.
    • Only origin/master exists: confirm the agent tries candidates in order and stops at the correct one.

@docker-agent docker-agent requested a review from a team as a code owner May 4, 2026 15:04
Copy link
Copy Markdown
Contributor

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🟡 NEEDS ATTENTION

2 medium findings (1 CONFIRMED, 1 LIKELY) and 1 low finding (LIKELY) in the changed code.

Comment thread review-pr/agents/pr-review.yaml Outdated
Comment thread review-pr/agents/pr-review.yaml Outdated
step 4) AND echoes it so you can see what was captured. The fallback order
prefers `upstream` (forks) over `origin`:
prompt says. Try each candidate ref in its own `shell` tool call, stopping at
the first that returns exit 0 with non-empty output. Candidates in order:
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.

[LIKELY LOW] No explicit instruction to continue when a git merge-base candidate returns non-zero exit

The instruction reads "stopping at the first that returns exit 0 with non-empty output" — describing the success condition but not the failure handling. The phrasing implicitly means "skip those that don't return exit 0", but it does not explicitly say "if a candidate fails (non-zero exit), that is expected — continue to the next one."

An LLM agent may interpret a non-zero exit from the shell tool as a fatal error and abort rather than advancing to the next candidate ref. Making the intent explicit would reduce ambiguity, e.g.:

"If the call returns a non-zero exit code or empty output, that ref does not exist — continue to the next candidate. Non-zero exit from git merge-base is expected and normal for missing refs."

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.

Clarified in dea7290. Added an explicit sentence after the cross-shell warning: "If a git merge-base call returns a non-zero exit code or empty output, that ref does not exist in this clone — that is expected; continue to the next candidate. Only the seventh candidate failing should be treated as an error."

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.

Perfect! The added instruction makes the failure handling explicit and removes ambiguity. The clarification that "only the seventh candidate failing should be treated as an error" also gives the agent a clear decision point. Thank you for addressing this!

…lity

- Edit 1: replace bash-only `ls AGENTS.md CLAUDE.md 2>/dev/null` in step 2 with prose directing the agent to use `read_file` for AGENTS.md then CLAUDE.md, with no shell redirection
- Edit 2: replace shell redirect `> ./pr-review.diff` with `git diff --output=./pr-review.diff` to avoid PowerShell 5.1 UTF-16 LE BOM encoding corruption; update step 4 sub-bullet accordingly
- Edit 3: add explicit guidance that a non-zero exit code from `git merge-base` is expected and means skip to the next candidate; only the seventh failure is an error

Assisted-By: docker-agent
@docker-agent
Copy link
Copy Markdown
Contributor Author

/review

Copy link
Copy Markdown
Contributor

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🟡 NEEDS ATTENTION

This PR successfully removes all bash-only constructs from the Console-mode merge-base detection — a clear improvement for cross-shell compatibility. One medium-severity gap was found in the added instructions.


If all seven candidates fail, issue a plain `echo` explaining that no known
remote or local branch was found, then stop calling tools.
If `BASE` equals the output of `git rev-parse HEAD`, issue a plain `echo`
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.

[MEDIUM] Missing instruction to run git rev-parse HEAD before the BASE == HEAD guard

The instruction tells the agent:

"If BASE equals the output of git rev-parse HEAD, issue a plain echo … then stop calling tools."

But the agent has no way to perform this comparison without first obtaining HEAD's SHA via an explicit shell tool call (git rev-parse HEAD). That call is never instructed. Since every shell invocation is a fresh shell (stated explicitly in the file), the agent cannot carry BASE across calls — it must compare two values in its LLM reasoning, which means it needs the HEAD SHA explicitly surfaced.

Without the instruction, the agent is left with two bad options:

  1. Skip the guard silently, proceeding with a potentially empty diff.
  2. Attempt [ "$BASE" = "$(git rev-parse HEAD)" ] — exactly the bash-only syntax this PR is trying to eliminate.

Suggested fix: Add an explicit step after the successful git merge-base call:

git rev-parse HEAD

Then tell the agent: "If this SHA matches BASE, issue a plain echo explaining the diff would be empty and stop."

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 e3e5e71. Added an explicit instruction after a git merge-base candidate succeeds: the agent now runs git rev-parse HEAD as a separate shell call, then compares it to BASE in its reasoning (not in shell). If equal, it echos a clear message ("you're on the default branch; check out a feature branch first") and stops. No bash-only test syntax involved.

@docker-agent
Copy link
Copy Markdown
Contributor Author

/review

Copy link
Copy Markdown
Contributor

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

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

Assessment: NEEDS ATTENTION

Comment thread review-pr/agents/pr-review.yaml Outdated
Otherwise, proceed to the diff capture by running these two commands, each as
its own `shell` call:
```
git diff <BASE> HEAD --output=./pr-review.diff
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.

[MEDIUM] git diff --output=<file> requires Git ≥ 2.25; older environments silently produce no diff file

The --output=<file> flag on git diff was introduced in Git 2.25 (January 2020). Several common environments ship older versions (e.g., Ubuntu 18.04 includes Git 2.17; older macOS Xcode CLT builds are also affected). If --output is not recognised, Git may silently ignore the flag or fail without writing ./pr-review.diff, while git diff --stat (the next step) succeeds anyway — so the agent sees a stat summary but delegates a non-existent file path to the drafter.

The cross-shell concern that motivated this choice does not actually block the simpler alternative: output redirection with > works in both bash and PowerShell 5.1, so:

git diff <BASE> HEAD > ./pr-review.diff

is both cross-shell compatible and universally supported across all Git versions.

Fix: Replace git diff <BASE> HEAD --output=./pr-review.diff with git diff <BASE> HEAD > ./pr-review.diff.

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.

Respectfully disagree on both points — keeping --output.

On the Git version requirement: I checked upstream sources and git diff --output=<file> has been parsed in diff.c since at least v2.0.0 (2014):

$ curl -sS https://raw.githubusercontent.com/git/git/v2.0.0/diff.c | grep -A2 '"output"'
else if ((argcount = parse_long_opt("output", av, &optarg))) {
    options->file = fopen(optarg, "w");

It's also present in v2.10.0, v2.17.0, v2.20.0 — well before 2.25. Ubuntu 18.04's Git 2.17 supports it fine. The 2.25 release notes don't mention any change to this option. I think this finding is based on a misattribution.

On > being cross-shell safe: This contradicts the previous review (#162 (comment)) which we accepted as valid. PowerShell 5.1's > operator is a shorthand for Out-File, which defaults to Unicode (UTF-16 LE) with BOM — confirmed in Microsoft's docs. On a Windows host running pr-review.yaml in Console mode, git diff … > ./pr-review.diff would write a UTF-16-encoded diff that the drafter sub-agent's read_file would then mis-parse. That's the bug the switch to --output was meant to prevent.

Since --output (a) is universally available in the Git versions anyone runs this agent against, and (b) bypasses the host shell entirely so encoding can't be re-mangled, it's strictly safer than > here. Happy to revisit if you have a counter-example for the version concern.

Copy link
Copy Markdown
Contributor

@docker-agent docker-agent Bot May 4, 2026

Choose a reason for hiding this comment

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

You're absolutely right on both counts — I apologize for the error.

I misattributed the Git 2.25 date based on faulty information. Your research shows --output has been in diff.c since at least Git 2.0.0 (2014), making it universally available in any environment that would run this agent. My claim about Ubuntu 18.04 compatibility was wrong.

The PowerShell 5.1 encoding issue is also spot-on. I should have read the earlier review thread more carefully before suggesting > as an alternative — the UTF-16 LE default encoding in PowerShell's Out-File is exactly the kind of subtle corruption that --output prevents by bypassing the shell entirely. Thank you for the detailed explanation and Microsoft docs reference.

Keeping --output is the correct choice here.

@docker-agent
Copy link
Copy Markdown
Contributor Author

/review

@docker-agent
Copy link
Copy Markdown
Contributor

docker-agent Bot commented May 4, 2026

⏭️ Review skipped — another review for this PR is already in progress (started 518s ago). Only one review runs at a time to avoid duplicate comments. The in-progress review will post its results when complete.

@derekmisler
Copy link
Copy Markdown
Contributor

/review

Copy link
Copy Markdown
Contributor

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🟢 APPROVE

The cross-shell compatibility fix is well-structured and correctly removes all bash-only constructs from Console-mode step 1. The --output= flag is used instead of shell > redirection (avoiding PowerShell's UTF-16 LE encoding issue), step 4b is updated to match, the safety clause is correctly scoped to Console mode only, and all 7 candidate refs are listed in the right order. No real bugs found in the changed code.

@derekmisler derekmisler merged commit eb20bcf into main May 4, 2026
27 checks passed
@derekmisler derekmisler deleted the fix/console-mode-cross-shell-compat branch May 4, 2026 15:48
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.

pr-review.yaml's Console-mode bash chains break local runs on Windows

2 participants