Repository navigation
Make safeoutputs CLI transport fail loudly instead of fail open #61427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
6b5ad0d
cc0d873
e786783
f83f180
4a8b4a9
13bdf8c
6042238
67f059e
a444378
556c34c
f0d8b29
cd61925
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…output reports Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2537,6 +2537,71 @@ describe("handle_agent_failure", () => { | |
| // buildEngineFailureContext | ||
| // ────────────────────────────────────────────────────── | ||
|
|
||
| describe("buildSafeOutputsCliInvocationContext", () => { | ||
| let buildSafeOutputsCliInvocationContext; | ||
| let isDroppedPipeSafeOutputsCommand; | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
| const os = require("os"); | ||
|
|
||
| /** @type {string} */ | ||
| let tmpDir; | ||
| /** @type {string} */ | ||
| let stdioLogPath; | ||
|
|
||
| beforeEach(() => { | ||
| vi.resetModules(); | ||
| tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "aw-test-")); | ||
| stdioLogPath = path.join(tmpDir, "agent-stdio.log"); | ||
| process.env.GH_AW_AGENT_OUTPUT = path.join(tmpDir, "agent_output.json"); | ||
| ({ buildSafeOutputsCliInvocationContext, isDroppedPipeSafeOutputsCommand } = require("./handle_agent_failure.cjs")); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| delete process.env.GH_AW_AGENT_OUTPUT; | ||
| if (fs.existsSync(tmpDir)) { | ||
| fs.rmSync(tmpDir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
|
|
||
| it("returns empty string when the transcript is missing", () => { | ||
| expect(buildSafeOutputsCliInvocationContext()).toBe(""); | ||
| }); | ||
|
|
||
| it("returns empty string when the transcript has no safeoutputs commands", () => { | ||
| fs.writeFileSync(stdioLogPath, "analysis complete\nnothing to report\n"); | ||
| expect(buildSafeOutputsCliInvocationContext()).toBe(""); | ||
| }); | ||
|
|
||
| it("lists safeoutputs commands found in the transcript", () => { | ||
| fs.writeFileSync(stdioLogPath, `printf '{"message":"no action needed"}' | safeoutputs noop .\n`); | ||
| const result = buildSafeOutputsCliInvocationContext(); | ||
| expect(result).toContain("safeoutputs` commands found in the agent transcript"); | ||
| expect(result).toContain("safeoutputs noop ."); | ||
| expect(result).not.toContain("never ran"); | ||
| }); | ||
|
|
||
| it("diagnoses a dropped pipe before safeoutputs", () => { | ||
| fs.writeFileSync(stdioLogPath, `printf '{"message":"no action needed"}' safeoutputs noop .\n`); | ||
| const result = buildSafeOutputsCliInvocationContext(); | ||
| expect(result).toContain("the CLI never ran"); | ||
| expect(result).toContain('safeoutputs <tool> \'{"key":"value"}\''); | ||
| }); | ||
|
|
||
| it("deduplicates repeated commands", () => { | ||
| fs.writeFileSync(stdioLogPath, `safeoutputs noop --message "done"\nsafeoutputs noop --message "done"\n`); | ||
| const result = buildSafeOutputsCliInvocationContext(); | ||
| const occurrences = result.split(`safeoutputs noop --message "done"`).length - 1; | ||
| expect(occurrences).toBe(1); | ||
| }); | ||
|
|
||
| it("classifies piped and unpiped invocations", () => { | ||
| expect(isDroppedPipeSafeOutputsCommand(`printf '{"message":"x"}' safeoutputs noop .`)).toBe(true); | ||
| expect(isDroppedPipeSafeOutputsCommand(`printf '{"message":"x"}' | safeoutputs noop .`)).toBe(false); | ||
| expect(isDroppedPipeSafeOutputsCommand(`safeoutputs noop --message "x"`)).toBe(false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] No regression test covers the false-positive case above (prose mentioning 💡 Suggested testit("does not misclassify prose containing 'safeoutputs' as a dropped pipe", () => {
expect(isDroppedPipeSafeOutputsCommand(`echo "See docs: safeoutputs add_comment example"`)).toBe(false);
});@copilot please address this. |
||
| }); | ||
| }); | ||
|
|
||
| describe("buildEngineFailureContext", () => { | ||
| let buildEngineFailureContext; | ||
| const fs = require("fs"); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnosing-bugs] The dropped-pipe heuristic can false-positive on prose.
isDroppedPipeSafeOutputsCommandonly checks forprintf/echofollowed bysafeoutputswith no|/;/&/>/<in between — it doesn't requiresafeoutputsto actually be the argument ofprintf/echo. A transcript line likeecho "See docs: safeoutputs add_comment example"gets misclassified as a dropped pipe, and the failure report tells the author "the CLI never ran" when it may never have been invoked at all here (it's just prose in an echo statement).💡 Reproduction
Consider requiring
safeoutputsto appear as a quoted-string boundary token (e.g. right after'/") rather than anywhere in the echo/printf argument, or tightening the regex to the actual invocation shape (safeoutputs <tool_name>) followed by no closing quote before it.@copilot please address this.