fix(action): reserve output for complete reasoning-model reviews (CW-6869) [skip ci]

Live run 40830663408 reached DeepSeek but used all 4000 automatically allowed output tokens and returned no review. Publication was skipped. Generate an explicit 16384-token allowance, configurable with --max-output-tokens, and retain sanitized reasoning counts. Recognize the released CLI infrastructure/not_produced envelope as incomplete without logging raw provider/model errors. Local verification: existing review checks 8 passed, 0 failed; direct failure-envelope classification and actionlint pass. The running workflow still references the prior reviewed Action commit; the next source pin update carries this repair.
This commit is contained in:
CodeWhale Bot committed 2026-10-28 23:44:53 -07:00
1 parent 43d8faead7
commit f4c2ce3a6e
4 files changed
+16 -4

No files matched your search

+1
View File
@@ -41,6 +41,7 @@ jobs:
version: 'v0.10.2'
provider: 'deepseek'
model: 'deepseek-flash'
max-output-tokens: '16384'
pr-number: ${{ inputs.pr-number }}
trigger-phrase: '@codewhale'
allow-forks: 'true'
+4 -1
View File
@@ -32,9 +32,12 @@ Ubuntu runner provides the review tools. No server or GitHub App is required.
Select your own provider and exact model ID. The generator pins both
Codewhale actions to the source checkout's full commit and the CLI to
`v0.10.2`. It refuses to overwrite an existing workflow. Use `--print` to
`v0.10.2`, with a 16384-token output allowance for the completed review.
It refuses to overwrite an existing workflow. Use `--print` to
inspect the YAML, `--version vX.Y.Z` to choose another released CLI, or
`--action-ref FULL_COMMIT_SHA` with a separately downloaded `setup.mjs`.
`--max-output-tokens N` changes the explicit output allowance. Reasoning
models can exhaust smaller automatic allowances before writing findings.
The generated workflow is `.github/workflows/codewhale-review.yml`.
2. Store only the selected provider's API key as a repository Actions secret:
+5 -1
View File
@@ -133,7 +133,7 @@ export function classify(result, head, post, base) {
const publication = ['not_requested', 'not_attempted', 'uncertain', 'posted'].includes(data?.publication)
? data.publication : 'unknown';
const usage = {};
for (const key of ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens']) {
for (const key of ['input_tokens', 'output_tokens', 'reasoning_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens']) {
if (Number.isSafeInteger(data?.usage?.[key]) && data.usage[key] >= 0) usage[key] = data.usage[key];
}
const evidence = { publication, usage };
@@ -144,6 +144,10 @@ export function classify(result, head, post, base) {
if (data?.success === true && data.complete === false) {
return { ...evidence, outcome: 'incomplete', reason: 'coverage_incomplete' };
}
if (data?.mode === 'review' && data.success === false && data.complete === false
&& data.outcome === 'infrastructure' && data.review_verdict === 'not_produced') {
return { ...evidence, outcome: 'incomplete', reason: 'review_not_produced' };
}
if (result.status !== 0 || data?.mode !== 'review' || data.success !== true || data.complete !== true
|| data.pr?.head_sha !== head || !Array.isArray(data.review?.issues)
|| !Number.isSafeInteger(data.review_passes) || data.review_passes < 1
+6 -2
View File
@@ -21,6 +21,7 @@ const HELP = `Create a Codewhale review bot workflow in your repository.
--action-ref SHA Reviewed Action commit (default: this clean source checkout)
--trigger-phrase NAME Mention to listen for (default: @codewhale)
--app-login LOGIN Optional existing GitHub App login, including [bot]
--max-output-tokens N Review output allowance (default: 16384; minimum: 8192)
--print Print YAML without writing files
--help Show this help
@@ -30,7 +31,7 @@ Automatic reviews cover same-repository PRs. Maintainers can review fork PRs
with a manual run or an @codewhale review comment; PR code is never executed.
`;
export function workflow({ provider, model, version = 'v0.10.2', actionRef, triggerPhrase = '@codewhale', appLogin }) {
export function workflow({ provider, model, version = 'v0.10.2', actionRef, triggerPhrase = '@codewhale', appLogin, maxOutputTokens = '16384' }) {
if (!Object.hasOwn(KEYS, provider)) throw new Error('Choose a supported --provider.');
if (!/^[A-Za-z0-9][A-Za-z0-9_./:@+-]{0,199}$/.test(model || '')
|| (provider === 'codewhale' && !/^[A-Za-z0-9_.-]+\/.+/.test(model))) throw new Error('Choose an exact --model ID.');
@@ -38,6 +39,7 @@ export function workflow({ provider, model, version = 'v0.10.2', actionRef, trig
if (!/^[a-f0-9]{40}$/i.test(actionRef || '')) throw new Error('--action-ref must be a full reviewed commit SHA.');
if (!/^@[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(triggerPhrase)) throw new Error('--trigger-phrase must be @ followed by a GitHub-style name.');
if (appLogin && !/^[a-zA-Z0-9][a-zA-Z0-9-]{0,99}\[bot\]$/.test(appLogin)) throw new Error('--app-login must include [bot].');
if (!/^[1-9][0-9]*$/.test(String(maxOutputTokens)) || Number(maxOutputTokens) < 8192 || Number(maxOutputTokens) > 1000000) throw new Error('--max-output-tokens must be between 8192 and 1000000.');
const action = `codewhale-hq/Codewhale@${actionRef}`;
const secret = KEYS[provider];
const expression = value => '${{ ' + value + ' }}';
@@ -84,6 +86,7 @@ jobs:
version: '${version}'
provider: '${provider}'
model: '${model}'
max-output-tokens: '${maxOutputTokens}'
pr-number: ${expression('inputs.pr-number')}
trigger-phrase: '${triggerPhrase}'
allow-forks: 'true'
@@ -151,6 +154,7 @@ export function setup(args = process.argv.slice(2)) {
provider: { type: 'string' }, model: { type: 'string' }, directory: { type: 'string' },
version: { type: 'string' }, 'action-ref': { type: 'string' }, 'trigger-phrase': { type: 'string' },
'app-login': { type: 'string' }, print: { type: 'boolean' }, help: { type: 'boolean' },
'max-output-tokens': { type: 'string' },
} });
if (values.help) { console.log(HELP); return; }
let actionRef = values['action-ref'];
@@ -164,7 +168,7 @@ export function setup(args = process.argv.slice(2)) {
actionRef = head.stdout.trim();
}
const yaml = workflow({ provider: values.provider, model: values.model, version: values.version,
actionRef, triggerPhrase: values['trigger-phrase'], appLogin: values['app-login'] });
actionRef, triggerPhrase: values['trigger-phrase'], appLogin: values['app-login'], maxOutputTokens: values['max-output-tokens'] });
if (values.print) { process.stdout.write(yaml); return; }
const directory = realpathSync(values.directory || process.cwd());
if (spawnSync('git', ['-C', directory, 'rev-parse', '--show-toplevel'], { encoding: 'utf8' }).stdout.trim() !== directory) throw new Error('--directory must be a Git repository root.');