Skip to content
Open
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
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ inputs:
description: 'Whether to fail the check run when findings exceed fail-on threshold'
required: false
default: 'false'
post-checks:
description: 'Whether to create/update GitHub Check runs (core "warden" check and per-skill "warden: <skill>" checks). Set to false to disable all check-run writes while still posting PR review comments.'
required: false
default: 'true'
parallel:
description: 'Maximum number of concurrent trigger executions'
required: false
Expand Down Expand Up @@ -89,5 +93,6 @@ runs:
INPUT_MAX_FINDINGS: ${{ inputs.max-findings }}
INPUT_REQUEST_CHANGES: ${{ inputs.request-changes }}
INPUT_FAIL_CHECK: ${{ inputs.fail-check }}
INPUT_POST_CHECKS: ${{ inputs.post-checks }}
INPUT_PARALLEL: ${{ inputs.parallel }}
run: node ${{ github.action_path }}/dist/action/index.js
1 change: 1 addition & 0 deletions packages/docs/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ jobs:
| `max-findings` | `50` | Maximum findings to report |
| `request-changes` | `false` | Whether to request changes on PR reviews |
| `fail-check` | `false` | Whether to fail the check run |
| `post-checks` | `true` | Whether to create/update the core `warden` check and per-skill checks. Set `false` to post only PR review comments — do not combine with a required Warden status check. |
| `parallel` | `5` | Maximum concurrent trigger executions |

### Split Analyze and Report
Expand Down
11 changes: 11 additions & 0 deletions packages/docs/src/content/docs/github/workflow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ workflows. Let Warden start, then let `warden.toml` decide which triggers match.
For each configured pull request trigger, Warden creates a check run. Triggers
that do not actually run for the current event complete as neutral.

Do not set `post-checks: false` on a repo where `warden` or a per-skill check
is a required status check — Warden stops creating that check entirely, so it
never reports and the required check blocks every PR indefinitely.

## Action Inputs

<dl class="sentry-key-value-list">
Expand Down Expand Up @@ -259,6 +263,13 @@ that do not actually run for the current event complete as neutral.
</dt>
<dd>Whether to fail the check run. Default: <code>false</code>.</dd>
</div>
<div>
<dt>
<code>post-checks</code>
<span class="sentry-property-meta">boolean</span>
</dt>
<dd>Whether to create/update the core <code>warden</code> check and per-skill checks. Set to <code>false</code> to post only PR review comments. Default: <code>true</code>. See <a href="#required-status-checks">Required Status Checks</a> before disabling this on a repo with a required Warden check.</dd>
</div>
<div>
<dt>
<code>parallel</code>
Expand Down
23 changes: 23 additions & 0 deletions packages/warden/src/action/inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ describe('parseActionInputs', () => {
const inputs = parseActionInputs();
expect(inputs.failCheck).toBeUndefined();
});

it('parses post-checks as true', () => {
process.env['INPUT_POST_CHECKS'] = 'true';
const inputs = parseActionInputs();
expect(inputs.postChecks).toBe(true);
});

it('parses post-checks as false', () => {
process.env['INPUT_POST_CHECKS'] = 'false';
const inputs = parseActionInputs();
expect(inputs.postChecks).toBe(false);
});

it('defaults postChecks to true when not set', () => {
const inputs = parseActionInputs();
expect(inputs.postChecks).toBe(true);
});
});

describe('numeric input handling', () => {
Expand Down Expand Up @@ -172,6 +189,7 @@ describe('setupAuthEnv', () => {
mode: 'run',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
});
expect(process.env['ANTHROPIC_API_KEY']).toBe('sk-ant-api-key');
Expand All @@ -187,6 +205,7 @@ describe('setupAuthEnv', () => {
mode: 'run',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
});
expect(process.env['CLAUDE_CODE_OAUTH_TOKEN']).toBe('sk-ant-oat-oauth-token');
Expand All @@ -205,6 +224,7 @@ describe('setupAuthEnv', () => {
mode: 'run',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
});

Expand All @@ -225,6 +245,7 @@ describe('setupAuthEnv', () => {
mode: 'run',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
});

Expand All @@ -244,6 +265,7 @@ describe('validateInputs', () => {
baseSkillRoot: '.warden-org',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
})).toThrow('base-skill-root requires base-config-path');
});
Expand All @@ -256,6 +278,7 @@ describe('validateInputs', () => {
mode: 'report',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
})).toThrow('findings-file is required when mode is report');
});
Expand Down
4 changes: 4 additions & 0 deletions packages/warden/src/action/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface ActionInputs {
requestChanges?: boolean;
/** Whether to fail the check run when findings exceed failOn */
failCheck?: boolean;
/** Whether to create/update GitHub Check runs. Default true; explicit false disables the core check and all per-skill checks. */
postChecks: boolean;
/** Max concurrent trigger executions */
parallel: number;
}
Expand Down Expand Up @@ -111,6 +113,7 @@ export function parseActionInputs(): ActionInputs {

const requestChanges = parseBooleanInput(getInput('request-changes'));
const failCheck = parseBooleanInput(getInput('fail-check'));
const postChecks = parseBooleanInput(getInput('post-checks')) ?? true;

return {
anthropicApiKey,
Expand All @@ -126,6 +129,7 @@ export function parseActionInputs(): ActionInputs {
maxFindings: Number.isNaN(maxFindingsParsed) ? 50 : maxFindingsParsed,
requestChanges,
failCheck,
postChecks,
parallel: Number.isNaN(parallelParsed) ? DEFAULT_CONCURRENCY : parallelParsed,
};
}
Expand Down
1 change: 1 addition & 0 deletions packages/warden/src/action/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const baseInputs: ActionInputs = {
mode: 'run',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
version = 1

[defaults]
postChecks = false

[defaults.auxiliary]
model = "anthropic/org-aux-model"
maxRetries = 7
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
version = 1

[defaults]
postChecks = true

[defaults.auxiliary]
model = "anthropic/repo-aux-model"
maxRetries = 2
Expand Down
1 change: 1 addition & 0 deletions packages/warden/src/action/workflow/base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ function createInputs(overrides: Partial<ActionInputs> = {}): ActionInputs {
mode: 'run',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 4,
...overrides,
};
Expand Down
121 changes: 121 additions & 0 deletions packages/warden/src/action/workflow/pr-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ function createDefaultInputs(overrides: Partial<ActionInputs> = {}): ActionInput
mode: 'run',
configPath: 'warden.toml',
maxFindings: 50,
postChecks: true,
parallel: 2,
...overrides,
};
Expand Down Expand Up @@ -1905,6 +1906,126 @@ describe('runPRWorkflow', () => {
})
);
});

it('does not create or update any checks when postChecks is false, but still posts the review', async () => {
const finding = createFinding();
const report = createSkillReport({ findings: [finding] });
mockRunSkillTask.mockResolvedValue({ name: 'test-trigger', report });

await runPRWorkflow(
mockOctokit,
createDefaultInputs({ postChecks: false }),
'pull_request',
EVENT_PAYLOAD_PATH,
FIXTURES_DIR
);

expect(mockOctokit.checks.create).not.toHaveBeenCalled();
expect(mockOctokit.checks.update).not.toHaveBeenCalled();
expect(mockOctokit.pulls.createReview).toHaveBeenCalledWith(
expect.objectContaining({
owner: 'test-owner',
repo: 'test-repo',
pull_number: 123,
commit_id: PR_HEAD_SHA,
})
);
});

it('warden.toml defaults.postChecks overrides the post-checks action input', async () => {
const finding = createFinding();
const report = createSkillReport({ findings: [finding] });
mockRunSkillTask.mockResolvedValue({ name: 'test-trigger', report });

const tempDir = mkdtempSync(join(tmpdir(), 'warden-post-checks-config-'));
writeFileSync(
join(tempDir, 'warden.toml'),
[
'version = 1',
'',
'[defaults]',
'postChecks = false',
'',
'[[skills]]',
'name = "test-skill"',
'',
'[[skills.triggers]]',
'type = "pull_request"',
'actions = ["opened", "synchronize"]',
'',
].join('\n')
);

try {
await runPRWorkflow(
mockOctokit,
createDefaultInputs({ postChecks: true }),
'pull_request',
EVENT_PAYLOAD_PATH,
tempDir
);
} finally {
rmSync(tempDir, { recursive: true, force: true });
}

expect(mockOctokit.checks.create).not.toHaveBeenCalled();
expect(mockOctokit.checks.update).not.toHaveBeenCalled();
expect(mockOctokit.pulls.createReview).toHaveBeenCalled();
});

it('report mode does not create or update any checks when postChecks is false, but still posts the review', async () => {
const finding = createFinding();
const report = createSkillReport({ findings: [finding] });
const findingsFile = writeFindingsArtifact([report], [
{
triggerName: 'test-skill',
skillName: 'test-skill',
report,
},
]);

try {
await runPRWorkflow(
mockOctokit,
createDefaultInputs({ mode: 'report', findingsFile, postChecks: false }),
'pull_request',
EVENT_PAYLOAD_PATH,
FIXTURES_DIR
);
} finally {
rmSync(dirname(findingsFile), { recursive: true, force: true });
}

expect(mockRunSkillTask).not.toHaveBeenCalled();
expect(mockOctokit.checks.create).not.toHaveBeenCalled();
expect(mockOctokit.checks.update).not.toHaveBeenCalled();
expect(mockOctokit.pulls.createReview).toHaveBeenCalledWith(
expect.objectContaining({
owner: 'test-owner',
repo: 'test-repo',
pull_number: 123,
commit_id: PR_HEAD_SHA,
})
);
});

it('org base config postChecks=false wins over a repo config postChecks=true', async () => {
mockRunSkillTask.mockResolvedValue({ name: 'test-trigger', report: createSkillReport() });

await runPRWorkflow(
mockOctokit,
createDefaultInputs({
baseConfigPath: '.warden-org/warden.toml',
baseSkillRoot: '.warden-org',
}),
'pull_request',
EVENT_PAYLOAD_PATH,
LAYERED_AUXILIARY_MODEL_FIXTURES_DIR
);

expect(mockOctokit.checks.create).not.toHaveBeenCalled();
expect(mockOctokit.checks.update).not.toHaveBeenCalled();
});
});

describe('event context building', () => {
Expand Down
Loading