From 993f530062620e1efa41cf617cc15bd33f530801 Mon Sep 17 00:00:00 2001 From: Ling-Sen Peng Date: Mon, 17 Aug 2026 14:28:02 -0700 Subject: [PATCH] fix(examples): make example 61's CLI usage deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chained GitHub pipeline failed in a retry loop: models wrapped compound commands in 'bash -c' (rejected — bash not allowlisted), the instructed step-3 command itself started with a VAR=$(...) assignment (also rejected), the coder had no file-writing command in its allowlist, and models invented placeholder cwd paths. Add shared TOOL RULES to every CLI stage (no shell wrappers, first word must be allowlisted, no placeholder cwd), rewrite the instructed commands to be validator-legal, and allowlist echo/printf for the coder. --- .../agents/61-github-coding-agent-chained.ts | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/examples/agents/61-github-coding-agent-chained.ts b/examples/agents/61-github-coding-agent-chained.ts index 7a5ab7bc..da85c541 100644 --- a/examples/agents/61-github-coding-agent-chained.ts +++ b/examples/agents/61-github-coding-agent-chained.ts @@ -17,6 +17,16 @@ import { Agent, AgentRuntime, OnTextMention, TextGate } from '@io-orkes/conducto const REPO = 'agentspan-ai/codingexamples'; const MODEL = 'anthropic/claude-sonnet-4-6'; +// LLMs habitually wrap compound commands in `bash -c '...'`, but bash is not +// on the allowlist and the run_command tool rejects it — which spirals into +// retry loops. Steer every CLI-using stage away from the wrapper. +const CLI_RULES = + '\n\nTOOL RULES:\n' + + '- To run a compound command (&&, |, $(...)), put the FULL command line in the `command` field and set shell=true.\n' + + '- NEVER wrap a command in `bash -c` or `sh -c` — bash/sh are not allowed commands and the call will be rejected.\n' + + '- The FIRST word of every command must be one of the allowed executables. Never start a command with a VAR=... assignment — run `mktemp -d` as its own command first and reuse its output instead.\n' + + '- Only set `cwd` to a REAL directory path you obtained from an earlier command output. Never invent placeholder paths like /path/to/repo — omit `cwd` entirely if you do not have one.'; + // -- Stage 1: Fetch issues --------------------------------------------------- /** Stop when the agent has produced the structured output with issue details. */ @@ -38,8 +48,11 @@ export const gitFetchIssues = new Agent({ ` gh issue view --repo ${REPO} --json number,title,body,author,labels\n\n` + `You MUST run this command — gh issue list only returns titles, not the issue body.\n` + `Read the JSON output carefully and extract the author login and the COMPLETE body text.\n\n` + - `Step 3 — create a branch and push it (one compound command, shell=true):\n` + - ` TMPDIR=$(mktemp -d) && gh repo clone ${REPO} "$TMPDIR" && cd "$TMPDIR" && git checkout -b fix/issue- && git push -u origin fix/issue- && echo "DONE"\n\n` + + `Step 3a — create a scratch directory (run exactly this, saving the output):\n` + + ` mktemp -d (set context_key to "working_dir")\n\n` + + `Step 3b — clone and push the branch (one compound command, shell=true, substituting\n` + + ` with the directory printed by step 3a):\n` + + ` gh repo clone ${REPO} && cd && git checkout -b fix/issue- && git push -u origin fix/issue-\n\n` + `Step 4 — respond with ONLY these lines (NO tool calls):\n` + ` REPO: ${REPO}\n` + ` BRANCH: fix/issue-\n` + @@ -50,7 +63,7 @@ export const gitFetchIssues = new Agent({ `RULES:\n` + `- Do NOT create files, commits, or pull requests.\n` + `- After step 3, you MUST stop using tools entirely. Just output text.\n` + - `- Include the COMPLETE issue body in DETAILS — the next stage needs it to implement the fix.`, + `- Include the COMPLETE issue body in DETAILS — the next stage needs it to implement the fix.` + CLI_RULES, cliConfig: { enabled: true, allowedCommands: ['gh', 'git', 'mktemp', 'ls'], allowShell: true, timeout: 60 }, credentials: ['GITHUB_TOKEN', 'GH_TOKEN'], maxTurns: 20, @@ -69,12 +82,14 @@ export const coderStage = new Agent({ 'You are a senior developer. Your input contains issue details from the previous stage\n' + 'including REPO, BRANCH, ISSUE, AUTHOR, DETAILS, and SUMMARY.\n\n' + '1. Read the DETAILS field carefully — it contains the full issue body with requirements.\n' + - '2. Clone the repo: gh repo clone /tmp/work && cd /tmp/work\n' + - '3. Check out the branch: git checkout \n' + - '4. Implement the fix according to ALL requirements in DETAILS.\n' + - '5. Commit and push your changes.\n' + - '6. Say HANDOFF_TO_QA with REPO, BRANCH, and a summary of CHANGES.', - cliConfig: { enabled: true, allowedCommands: ['gh', 'git', 'mktemp', 'rm', 'ls', 'cat', 'mkdir', 'cp'], allowShell: true, timeout: 120 }, + '2. Create a fresh scratch directory: run `mktemp -d` (set context_key to "working_dir").\n' + + '3. Clone and check out the branch (substitute with the step-2 output):\n' + + ' gh repo clone && cd && git checkout \n' + + '4. Implement the fix according to ALL requirements in DETAILS. Satisfy every acceptance\n' + + ' criterion literally, even if the repo already partially meets it.\n' + + '5. Commit and push your changes (run from ): git add -A && git commit -m "Fix " && git push\n' + + '6. Say HANDOFF_TO_QA with REPO, BRANCH, and a summary of CHANGES.' + CLI_RULES, + cliConfig: { enabled: true, allowedCommands: ['gh', 'git', 'mktemp', 'rm', 'ls', 'cat', 'mkdir', 'cp', 'echo', 'printf'], allowShell: true, timeout: 120 }, }); export const qaStage = new Agent({ @@ -84,7 +99,7 @@ export const qaStage = new Agent({ instructions: 'You are a QA engineer. Clone the repo, review changes, run tests.\n' + 'If bugs found: say HANDOFF_TO_CODER with what to fix.\n' + - 'If good: say QA_APPROVED with REPO/BRANCH/SUMMARY.', + 'If good: say QA_APPROVED with REPO/BRANCH/SUMMARY.' + CLI_RULES, cliConfig: { enabled: true, allowedCommands: ['gh', 'git', 'mktemp', 'rm', 'ls', 'cat'], allowShell: true, timeout: 120 }, maxTokens: 60000, maxTurns: 15, @@ -125,7 +140,7 @@ export const gitPushPR = new Agent({ 'Create a pull request. Extract REPO, BRANCH, and ISSUE from the previous stage output.\n\n' + 'Run this command (shell=true so quotes are handled correctly):\n' + ' gh pr create --repo --base main --head --title "Fix " --body "Fixes "\n\n' + - 'After the command succeeds, STOP calling tools and respond with ONLY the PR URL.', + 'After the command succeeds, STOP calling tools and respond with ONLY the PR URL.' + CLI_RULES, cliConfig: { enabled: true, allowedCommands: ['gh', 'git'], allowShell: true, timeout: 60 }, stopWhen: prDone, });