{"type":"assistant.message","timestamp":"2026-08-04T13:15:35.205Z","data":{"content":"`tool_choice: \"required\"` works. The issue is that with `auto`, the model chose to respond with text. I need to use `required` when the agent has tools that need to be called. But this is tricky - after tool calls, the model should respond with text. Let me update the engine to be smarter:\n\nThe real approach: when there are tools defined for this agent and we haven't seen a tool call yet in the first turn, force `tool_choice: \"required\"` for the first API call. After the tool runs and returns, subsequent calls should use `auto`.\n\nActually a simpler fix: my engine should keep looping if it gets a non-JSON response. But the issue is the model itself decided not to call the tool. The prompt says \"Call run_random_samples exactly once with an empty object\" - but the model said \"I'll call it\" without actually calling it.\n\nThe cleanest solution is: when there are tools and no tool has been called yet, use `tool_choice: \"required\"`:"}}
{"type":"tool.execution_start","timestamp":"2026-08-04T13:15:35.206Z","data":{"toolName":"view","mcpServerName":""}}
{"type":"tool.execution_complete","timestamp":"2026-08-04T13:15:35.257Z","data":{"toolName":"view","mcpServerName":"","success":true,"result":{"content":"37. max_tokens: 8192,\n38. };\n39. if (toolDefs.length > 0) {\n40. body[\"tools\"] = toolDefs;\n41. body[\"tool_choice\"] = \"auto\";\n42. }\n43. \n44. const response = await fetch(\"http://api-proxy:10002/chat/completions\", {\n45. method: \"POST\",\n46. headers: {\n47. \"Content-Type\": \"application/json\",\n48. \"Authorization\": `******\n49. },\n50. body: JSON.stringify(body),\n51. signal: askOptions.signal,\n52. });\n53. \n54. if (!response.ok) {\n55. throw new Error(`API error: ${response.status} ${await response.text()}`);\n56. }\n57. \n58. const data = await response.json() as any;\n59. const choice = data.choices?.[0];\n60. const message = choice?.message;","detailedContent":"\ndiff --git a/home/runner/work/rig/rig/.tmp/run_samples4.ts b/home/runner/work/rig/rig/.tmp/run_samples4.ts\nindex 0000000..0000000 100644\n--- a/home/runner/work/rig/rig/.tmp/run_samples4.ts\n+++ b/home/runner/work/rig/rig/.tmp/run_samples4.ts\n@@ -37,24 +37,24 @@\n max_tokens: 8192,\n };\n if (toolDefs.length > 0) {\n body[\"tools\"] = toolDefs;\n body[\"tool_choice\"] = \"auto\";\n }\n \n const response = await fetch(\"http://api-proxy:10002/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `******\n },\n body: JSON.stringify(body),\n signal: askOptions.signal,\n });\n \n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${await response.text()}`);\n }\n \n const data = await response.json() as any;\n const choice = data.choices?.[0];\n const message = choice?.message;\n"}}}
{"type":"assistant.message","timestamp":"2026-08-04T13:16:04.968Z","data":{"content":""}}
{"type":"tool.execution_start","timestamp":"2026-08-04T13:16:04.970Z","data":{"toolName":"bash","mcpServerName":"","command":"cd /home/runner/work/rig/rig && cat > .tmp/run_samples5.ts << 'PROGRAM_EOF'\nimport { agent, configureAgent, defineTool, p, s } from \"rig\";\nimport type { AgentFactory } from \"rig\";\n\nconst simpleEngine: AgentFactory = async (agentOptions) => {\n const messages: Array<{ role: string; content: string | null; tool_calls?: any[] }> = [];\n const tools = agentOptions.tools ?? [];\n const systemMessage = typeof agentOptions.systemMessage === \"string\" ? agentOptions.systemMessage : undefined;\n\n const toolDefs = tools.map((tool) => ({\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description ?? \"\",\n parameters: tool.inputSchema ?? { type: \"object\", properties: {} },\n },\n }));\n\n return {\n async ask(prompt, askOptions = {}) {\n const requestMessages: any[] = [\n ...(systemMessage ? [{ role: \"system\", content: systemMessage }] : []),\n ...messages,\n { role: \"user\", content: prompt },\n ];\n\n let currentMessages: any[] = [...requestMessages];\n let iterations = 0;\n const maxIterations = 30;\n let toolCalledThisTurn = false;\n\n while (iterations < maxIterations) {\n iterations++;\n const body: Record<string, unknown> = {\n model: \"claude-sonnet-4.6\",\n messages: currentMessages,\n max_tokens: 8192,\n };\n if (toolDefs.length > 0) {\n body[\"tools\"] = toolDefs;\n // Force tool call on first iteration if tools haven't been called yet\n body[\"tool_choice\"] = (!toolCalledThisTurn && iterations === 1) ? \"required\" : \"auto\";\n }\n\n const response = await fetch(\"http://api-proxy:10002/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${process.env.COPILOT_DUMMY_BYOK}`,\n },\n body: JSON.stringify(body),\n signal: askOptions.signal,\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${await response.text()}`);\n }\n\n const data = await response.json() as any;\n const choice = data.choices?.[0];\n const message = choice?.message;\n\n if (!message) {\n throw new Error(\"No message in response\");\n }\n\n currentMessages = [...currentMessages, message];\n\n if (message.tool_calls && message.tool_calls.length > 0) {\n toolCalledThisTurn = true;\n const toolResults = [];\n for (const toolCall of message.tool_calls) {\n const tool = tools.find((t) => t.name === toolCall.function.name);\n if (!tool || !tool.handler) {\n toolResults.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n content: `Error: unknown tool ${toolCall.function.name}`,\n });\n continue;\n }\n let args: unknown;\n try {\n args = JSON.parse(toolCall.function.arguments);\n } catch {\n args = {};\n }\n let result: unknown;\n try {\n result = await tool.handler(args as any);\n } catch (e: any) {\n result = { error: e.message };\n }\n toolResults.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n content: JSON.stringify(result),\n });\n }\n currentMessages = [...currentMessages, ...toolResults];\n continue;\n }\n\n const text = message.content ?? \"\";\n messages.push({ role: \"user\", content: prompt }, { role: \"assistant\", content: text });\n return text;\n }\n throw new Error(\"Max iterations exceeded\");\n },\n async close() {},\n };\n};\n\nconfigureAgent(simpleEngine);\n\nconst SampleRun = s.object({\n path: s.path,\n status: s.enum(\"succeeded\", \"failed\"),\n output: s.string,\n logs: s.array(s.string),\n});\n\nconst runRigSample = defineTool(\"run_rig_sample\", {\n description: \"Execute one Rig sample and capture its stdout and stderr logs.\",\n parameters: s.object({ path: s.path }),\n async handler({ path }) {\n const { spawn } = await import(\"node:child_process\");\n const { readFile } = await import(\"node:fs/promises\");\n const { resolve, sep } = await import(\"node:path\");\n\n const samplesDirectory = resolve(\"skills/rig/samples\");\n const samplePath = resolve(path);\n if (!samplePath.startsWith(`${samplesDirectory}${sep}`) || !samplePath.endsWith(\".md\")) {\n throw new Error(`Sample path is outside skills/rig/samples: ${path}`);\n }\n\n const markdown = await readFile(samplePath, \"utf8\");\n const program = markdown.match(/^```rig[ \\t]*\\r?\\n([\\s\\S]*?)^```[ \\t]*\\r?$/m)?.[1];\n if (!program) {\n return { path, status: \"failed\", output: \"\", logs: [\"No rig fenced block found.\"] };\n }\n\n return await new Promise((resolveRun, rejectRun) => {\n const child = spawn(process.execPath, [\"skills/rig/rig.ts\", \"--server\"], {\n cwd: process.cwd(),\n env: {\n ...process.env,\n RIG_DEBUG: \"agent:invoke,agent:retry,agent:error,agent:failure,agent:close,engine:copilot:create,engine:copilot:close\",\n },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout.on(\"data\", (chunk: Buffer) => { stdout += String(chunk); });\n child.stderr.on(\"data\", (chunk: Buffer) => { stderr += String(chunk); });\n child.on(\"error\", rejectRun);\n child.on(\"close\", (code: number | null) => {\n resolveRun({\n path,\n status: code === 0 ? \"succeeded\" : \"failed\",\n output: stdout.trim(),\n logs: stderr.split(\"\\n\").filter(Boolean),\n });\n });\n child.stdin.end(program);\n });\n },\n});\n\nconst runSample = agent({\n name: \"sample-runner\",\n model: \"small\",\n input: s.object({ path: s.path }),\n tools: [runRigSample],\n instructions: p`Call run_rig_sample exactly once with ${p.inputField(\"path\")}.\nReturn the tool result unchanged. Do not run any other sample.`,\n output: SampleRun,\n});\n\nconst runRandomSamples = defineTool(\"run_random_samples\", {\n description: \"Randomly select five Rig samples and delegate each to sample-runner.\",\n parameters: s.object({}),\n async handler() {\n const { randomInt } = await import(\"node:crypto\");\n const { readdir } = await import(\"node:fs/promises\");\n const names = (await readdir(\"skills/rig/samples\"))\n .filter((name: string) => name.endsWith(\".md\"));\n if (names.length < 5) {\n throw new Error(`Expected at least 5 Rig samples, found ${names.length}.`);\n }\n for (let index = names.length - 1; index > 0; index -= 1) {\n const other = randomInt(index + 1);\n [names[index], names[other]] = [names[other]!, names[index]!];\n }\n const runs = [];\n for (const name of names.slice(0, 5)) {\n runs.push(await runSample({ path: `skills/rig/samples/${name}` }));\n }\n return { runs };\n },\n});\n\n// Agent role: randomly select five Rig samples, delegate every run, and aggregate the results.\nconst sampleCoordinator = agent({\n name: \"sample-coordinator\",\n model: \"small\",\n agents: { runSample },\n tools: [runRandomSamples],\n instructions: \"Call run_random_samples exactly once with an empty object and return its result unchanged.\",\n output: s.object({\n runs: s.array(SampleRun),\n }),\n});\n\nexport default sampleCoordinator;\nPROGRAM_EOF\necho \"done\""}}
{"type":"tool.execution_complete","timestamp":"2026-08-04T13:16:04.995Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"done\n<shellId: 63 completed with exit code 0>","detailedContent":"done\n<shellId: 63 completed with exit code 0>"}}}
{"type":"assistant.message","timestamp":"2026-08-04T13:16:10.857Z","data":{"content":""}}
{"type":"tool.execution_start","timestamp":"2026-08-04T13:16:10.859Z","data":{"toolName":"bash","mcpServerName":"","command":"cd /home/runner/work/rig/rig && echo \"run\" | RIG_DEBUG=\"agent:invoke,agent:retry,agent:error,agent:failure,agent:close\" node skills/rig/rig.ts .tmp/run_samples5.ts 2>/tmp/gh-aw/agent/stderr7.log | tee /tmp/gh-aw/agent/stdout7.log; echo \"EXIT:$?\""}}
{"type":"tool.execution_complete","timestamp":"2026-08-04T13:16:29.685Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"EXIT:0\n<shellId: run8 completed with exit code 0>","detailedContent":"EXIT:0\n<shellId: run8 completed with exit code 0>"}}}
[copilot-sdk-driver] [sdk-driver] error: Authorization error, you may need to run /login (Request ID: 74d55ceb-2e8d-405b-9b1d-85397a271f65)
Workflow Failure
Workflow: Daily Rig Sample Report
Branch: main
Run: https://github.com/githubnext/rig/actions/runs/30912003849
Warning
Engine Failure: The
copilotengine terminated unexpectedly.Last agent output:
Action Required
Assign this issue to an agent to debug and fix the issue.
Debug with any coding agent
Use this prompt with any coding agent (GitHub Copilot, Claude, Gemini, etc.):
Manually invoke the agent
Debug this workflow failure using your favorite Agent CLI and the
agentic-workflowsprompt.agentic-workflowsskill from.github/skills/agentic-workflows/SKILL.mdor https://github.com/github/gh-aw/blob/main/.github/skills/agentic-workflows/SKILL.mddebug the agentic workflow daily-rig-sampler failure in https://github.com/githubnext/rig/actions/runs/30912003849Tip
Stop reporting this workflow as a failure
To stop a workflow from creating failure issues, set
report-failure-as-issue: falsein its frontmatter: