From a0892cb78a504bac6be8a5727a2a50ad8ee8cf1a Mon Sep 17 00:00:00 2001 From: JUN Date: Sat, 12 Sep 2026 10:14:14 +0900 Subject: [PATCH] fix(devin): stop Cognition refusing every Codex turn over two tool descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex's built-in `exec_command` and `write_stdin` descriptions are on Cognition's tool-description blocklist verbatim, so the cloud answered every Codex request with `permission_denied` — a bare "hi" included — while the same account served a hand-built request carrying an ordinary shell tool. Add both sentences to COGNITION_BLOCKLIST_REWRITES, swapping only the leading verb. Unlike the Claude Code entry already in the table, these two are matched case-insensitively and tolerate a doubled interior space or a missing comma, so they match with flexible whitespace rather than exact bytes. The blocklist error also replaced Cognition's own message instead of carrying it, which left the misattribution invisible; it is now appended alongside the trace ID. --- src/adapters/devin/cloud-direct/chat.ts | 30 +++++++++++++++++++++++++ tests/providers/devin-adapter.test.ts | 30 ++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/adapters/devin/cloud-direct/chat.ts b/src/adapters/devin/cloud-direct/chat.ts index a42fd95646..334156daf9 100644 --- a/src/adapters/devin/cloud-direct/chat.ts +++ b/src/adapters/devin/cloud-direct/chat.ts @@ -505,9 +505,34 @@ const MAX_TOOL_DESC_LEN = 6998; * Cognition-specific constraint alongside the length limit above; if * Cognition adds more blocklisted phrases, extend this table and add a * regression test in tests/devin-adapter.test.ts. + * + * Not every entry is matched the same way. The Claude Code phrase above is + * case-sensitive and whitespace-exact, but the two Codex entries below are + * not: against a live account, lowercasing the first word and doubling an + * interior space both still produced `permission_denied`, while changing any + * single word passed. So those two match case-insensitively with flexible + * whitespace and an optional comma, and the rewrite swaps only the leading + * verb — the smallest edit measured to clear the filter. + * + * These two sentences are Codex's own built-in `exec_command` and + * `write_stdin` descriptions, verbatim. Every Codex turn carries them, so + * before this table knew about them the cloud refused literally every request + * from a Codex client — a bare "hi" included — while the same account + * answered a hand-built request with an ordinary shell tool. The visible + * symptom was the adapter's own blocklist message pointing back at this + * table, which is why they are named here rather than left to the next person + * to re-bisect. */ const COGNITION_BLOCKLIST_REWRITES: ReadonlyArray<[RegExp, string]> = [ [/\bTakes a task_id parameter identifying the task\b/g, "Accepts a task_id parameter identifying the task"], + [ + /\bRuns\s+a\s+command\s+in\s+a\s+PTY,?\s+returning\s+output\s+or\s+a\s+session\s+ID\s+for\s+ongoing\s+interaction\b/gi, + "Executes a command in a PTY, returning output or a session ID for ongoing interaction", + ], + [ + /\bWrites\s+characters\s+to\s+an\s+existing\s+unified\s+exec\s+session\s+and\s+returns\s+recent\s+output\b/gi, + "Sends characters to an existing unified exec session and returns recent output", + ], ]; function sanitizeToolDescriptionForCognition(description: string): string { @@ -1203,6 +1228,11 @@ export async function* streamChatEvents(req: CloudChatRequest): AsyncGenerator { // Descriptions without the trigger pass through unchanged expect(sanitizeToolDescriptionForCognitionForTests("A benign description.")).toBe("A benign description."); }); -}); + test("rewrites the Codex built-in tool descriptions Cognition refuses", () => { + // These two are Codex's own exec_command and write_stdin descriptions, + // verbatim. Every Codex turn carries them, so leaving them intact made the + // cloud refuse every request from a Codex client, a bare "hi" included. + // Measured against a live account: the sentences below were refused, and + // the rewritten forms were accepted. + const execCommand = "Runs a command in a PTY, returning output or a session ID for ongoing interaction."; + expect(sanitizeToolDescriptionForCognitionForTests(execCommand)) + .toBe("Executes a command in a PTY, returning output or a session ID for ongoing interaction."); + + const writeStdin = "Writes characters to an existing unified exec session and returns recent output."; + expect(sanitizeToolDescriptionForCognitionForTests(writeStdin)) + .toBe("Sends characters to an existing unified exec session and returns recent output."); + + // Cognition matches these two case-insensitively and tolerates both a + // doubled interior space and a missing comma, so the rewrite has to reach + // every variant that still gets refused rather than only the exact bytes. + expect(sanitizeToolDescriptionForCognitionForTests(execCommand.toLowerCase())) + .toContain("Executes a command in a PTY"); + expect(sanitizeToolDescriptionForCognitionForTests( + "Runs a command in a PTY returning output or a session ID for ongoing interaction.", + )).toContain("Executes a command in a PTY"); + + // Changing any single word already clears the filter, so a description that + // merely resembles these must survive untouched. + const nearMiss = "Runs a command in a terminal, returning output or a session ID for ongoing interaction."; + expect(sanitizeToolDescriptionForCognitionForTests(nearMiss)).toBe(nearMiss); + }); +});