Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "0.1.43"
version = "0.1.44"
edition = "2024"
rust-version = "1.85"
license = "Apache-2.0"
Expand Down
8 changes: 6 additions & 2 deletions crates/cli-sub-agent/src/debate_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,10 +893,14 @@ async fn wait_for_still_working_backoff() {
/// The debate tool loads the debate skill from the project's `.claude/skills/`
/// directory and follows its instructions autonomously. We only pass parameters.
fn build_debate_instruction(question: &str, is_continuation: bool, rounds: u32) -> String {
// Anti-recursion guard (see GitHub issue #272).
let prefix = "CRITICAL: You are running INSIDE a CSA subprocess as the debate agent. \
Do NOT run `csa run`, `csa review`, `csa debate`, or ANY `csa` command — \
this would cause infinite recursion. Read files and run git commands directly.\n\n";
if is_continuation {
format!("Use the debate skill. continuation=true. rounds={rounds}. question={question}")
format!("{prefix}Use the debate skill. continuation=true. rounds={rounds}. question={question}")
} else {
format!("Use the debate skill. rounds={rounds}. question={question}")
format!("{prefix}Use the debate skill. rounds={rounds}. question={question}")
}
}

Expand Down
12 changes: 10 additions & 2 deletions crates/cli-sub-agent/src/review_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,9 +636,17 @@ fn build_review_instruction(
security_mode: &str,
context: Option<&str>,
) -> String {
let mut instruction = format!(
"Use the csa-review skill. scope={scope}, mode={mode}, security_mode={security_mode}."
// Anti-recursion guard: tell the review tool it is running INSIDE a CSA
// subprocess so it must NOT invoke `csa run/review/debate` (which would
// cause infinite recursion — see GitHub issue #272).
let mut instruction = String::from(
"CRITICAL: You are running INSIDE a CSA subprocess as the review agent. \
Do NOT run `csa run`, `csa review`, `csa debate`, or ANY `csa` command — \
this would cause infinite recursion. Read files and run git commands directly.\n\n",
);
instruction.push_str(&format!(
"Use the csa-review skill. scope={scope}, mode={mode}, security_mode={security_mode}."
));
if let Some(ctx) = context {
instruction.push_str(&format!(" context={ctx}"));
}
Expand Down
4 changes: 3 additions & 1 deletion crates/csa-acp/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,9 @@ impl AcpConnection {
.try_wait()
.map_err(|err| AcpError::ConnectionFailed(err.to_string()))?
{
return Err(AcpError::ProcessExited(status.code().unwrap_or(-1)));
let code = status.code().unwrap_or(-1);
let stderr = self.stderr();
return Err(AcpError::ProcessExited { code, stderr });
}
Ok(())
}
Expand Down
14 changes: 12 additions & 2 deletions crates/csa-acp/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
use thiserror::Error;

fn format_stderr(stderr: &str) -> String {
if stderr.is_empty() {
String::new()
} else {
let last_lines: Vec<&str> = stderr.lines().rev().take(10).collect();
let summary: String = last_lines.into_iter().rev().collect::<Vec<_>>().join("\n");
format!("; stderr:\n{summary}")
}
}

#[derive(Error, Debug)]
pub enum AcpError {
#[error("ACP connection failed: {0}")]
Expand All @@ -10,8 +20,8 @@ pub enum AcpError {
SessionFailed(String),
#[error("ACP prompt failed: {0}")]
PromptFailed(String),
#[error("ACP process exited unexpectedly: code {0}")]
ProcessExited(i32),
#[error("ACP process exited unexpectedly: code {code}{}", format_stderr(.stderr))]
ProcessExited { code: i32, stderr: String },
#[error("Session fork failed: {0}")]
ForkFailed(String),
#[error("ACP subprocess spawn failed: {0}")]
Expand Down
6 changes: 6 additions & 0 deletions patterns/pr-codex-bot/PATTERN.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ Fix issues found by local review. Loop until clean (max 3 rounds).

> **Layer**: 0 (Orchestrator) -- shell commands only, no code reading/writing.

**Precondition**: Step 2 (Local Pre-PR Review) MUST have completed successfully.
If `LOCAL_REVIEW_HAS_ISSUES` is set, this step is blocked until Step 3 resolves
all issues. The orchestrator MUST NOT create a PR with unresolved local review
issues — this enforces the two-layer review architecture.

Condition: `!(${LOCAL_REVIEW_HAS_ISSUES})`
Tool: bash
OnFail: abort

Expand Down
1 change: 1 addition & 0 deletions patterns/pr-codex-bot/workflow.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ retry = 3
id = 4
title = "Push and Create PR"
tool = "bash"
condition = "!(${LOCAL_REVIEW_HAS_ISSUES})"
prompt = """
```bash
git push -u origin "${WORKFLOW_BRANCH}"
Expand Down
Loading