Skip to content
Merged
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
2 changes: 1 addition & 1 deletion DiffPalReviewDevV1/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 1,
"Minor": 6,
"Patch": 9
"Patch": 10
},
"minimumAgentVersion": "3.224.0",
"instanceNameFormat": "DiffPal Review Dev",
Expand Down
2 changes: 1 addition & 1 deletion DiffPalReviewV1/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 1,
"Minor": 6,
"Patch": 9
"Patch": 10
},
"minimumAgentVersion": "3.224.0",
"instanceNameFormat": "DiffPal Review",
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ steps:

When blocking findings are present, the task fails with a human-readable gate
message and preserves the non-zero DiffPal exit code for pipeline control.
Transient provider failures, including empty or invalid structured review
responses after retries, also fail with a human-readable task message while
preserving DiffPal's exit code.

### Blocking gate

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@
"publish:prod": "npm run build \u0026\u0026 tfx extension publish --manifest-globs vss-extension.json",
"smoke": "npm run build \u0026\u0026 node scripts/smoke-test.js"
},
"version": "0.1.31"
"version": "0.1.32"
}
25 changes: 25 additions & 0 deletions scripts/smoke-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,30 @@ function testGateFailureUsesHumanMessageForReviewBlockedExitCode() {
assert(!result.stdout.includes("diffpal exited with code 10"), "gate failure should not fall back to the generic exit code message");
}

function testTransientStructuredOutputFailureUsesHumanMessage() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "diffpal-ado-transient-structured-"));
const diffpalArgv = path.join(dir, "diffpal-argv");
const customDiffPal = path.join(dir, "diffpal");
makeFakeDiffPal(customDiffPal, diffpalArgv, {
exitCode: 3,
stderr: [
"diffpal: transient: validate structured output: structured output schema validation error\n",
"structured I/O schema validation error\n",
"extract output JSON: output is empty\n"
].join("")
});

const result = runHandlerExpectFailure("transient structured output failure", {
INPUT_INSTALL: "false",
INPUT_DIFFPALPATH: customDiffPal,
INPUT_GATE: "true"
});

assert(result.status === 3, `transient failure exit code should be preserved, got ${result.status}`);
assert(result.stdout.includes("DiffPal review could not complete because the provider returned an empty or invalid structured response after retries."), "transient structured output failure should be human-readable");
assert(!result.stdout.includes("diffpal exited with code 3"), "transient structured output failure should not use the generic exit code message");
}

function testNonGateFailureStaysGeneric() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "diffpal-ado-generic-failure-"));
const diffpalArgv = path.join(dir, "diffpal-argv");
Expand Down Expand Up @@ -558,5 +582,6 @@ testExplicitRangeBypassesGitResolution();
testMissingTargetRefExplainsFetchFailure();
testExplainPrintsResolvedContext();
testGateFailureUsesHumanMessageForReviewBlockedExitCode();
testTransientStructuredOutputFailureUsesHumanMessage();
testNonGateFailureStaysGeneric();
console.log("Azure DevOps task smoke tests passed");
32 changes: 27 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type ReviewRange = {
};

const DEFAULT_DIFFPAL_VERSION = "0.1.31";
const TRANSIENT_REVIEW_EXIT_CODE = 3;
const REVIEW_BLOCKED_EXIT_CODE = 10;

function input(name: string): string {
Expand Down Expand Up @@ -292,6 +293,31 @@ function isReviewBlockedFailure(code: number): boolean {
return code === REVIEW_BLOCKED_EXIT_CODE;
}

function isTransientReviewFailure(code: number): boolean {
return code === TRANSIENT_REVIEW_EXIT_CODE;
}

function hasStructuredOutputFailure(output: string): boolean {
const normalized = output.toLowerCase();
return normalized.includes("structured output") ||
normalized.includes("output is empty") ||
normalized.includes("no json object");
}

function diffPalFailureMessage(result: CommandResult, gate: boolean, blockOn: string): string {
if (gate && isReviewBlockedFailure(result.code)) {
return `DiffPal code review found blocking issues at or above the ${blockOn} threshold.`;
}
if (isTransientReviewFailure(result.code)) {
const output = `${result.stdout}\n${result.stderr}`;
if (hasStructuredOutputFailure(output)) {
return "DiffPal review could not complete because the provider returned an empty or invalid structured response after retries. Rerun the pipeline or check provider availability, auth, and quota.";
}
return "DiffPal review could not complete because the provider failed transiently after retries. Rerun the pipeline or check provider availability, auth, and quota.";
}
return `diffpal exited with code ${result.code}`;
}

async function fetchTargetBranch(targetBranch: string): Promise<void> {
if (!targetBranch) {
return;
Expand Down Expand Up @@ -469,11 +495,7 @@ async function run(): Promise<void> {
const result = await spawnCommandWithCapture(command, args);
if (result.code !== 0) {
process.exitCode = result.code;
if (gate && isReviewBlockedFailure(result.code)) {
tl.setResult(tl.TaskResult.Failed, `DiffPal code review found blocking issues at or above the ${blockOn} threshold.`);
return;
}
tl.setResult(tl.TaskResult.Failed, `diffpal exited with code ${result.code}`);
tl.setResult(tl.TaskResult.Failed, diffPalFailureMessage(result, gate, blockOn));
return;
}
tl.setResult(tl.TaskResult.Succeeded, "DiffPal review completed");
Expand Down
2 changes: 1 addition & 1 deletion vss-extension.dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@
"id": "Microsoft.VisualStudio.Services"
}
],
"version": "0.1.31"
"version": "0.1.32"
}
2 changes: 1 addition & 1 deletion vss-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@
"id": "Microsoft.VisualStudio.Services"
}
],
"version": "0.1.31"
"version": "0.1.32"
}