diff --git a/packages/docs/src/pages/cli.astro b/packages/docs/src/pages/cli.astro index d2fdd118..f7f60ae6 100644 --- a/packages/docs/src/pages/cli.astro +++ b/packages/docs/src/pages/cli.astro @@ -136,7 +136,7 @@ warden setup-app --org my-org # For an organization`}
  • -m, --model <model> - Model to use (fallback when not set in config)
  • -o, --output <path> - Write full run output to a JSONL file
  • --report-on <severity> - Only show findings at or above this severity in output
  • -
  • --parallel <n> - Max concurrent trigger/skill executions (default: 4)
  • +
  • --parallel <n> - Max concurrent file analyses across running skills (default: 4)
  • --quiet - Errors and final summary only
  • -vv - Show debug info (token counts, latencies)
  • --color / --no-color - Override color detection
  • diff --git a/packages/docs/src/pages/config.astro b/packages/docs/src/pages/config.astro index 7caf0297..c8b45079 100644 --- a/packages/docs/src/pages/config.astro +++ b/packages/docs/src/pages/config.astro @@ -13,6 +13,7 @@ const tocItems = [ { href: '#filters', title: 'Filters' }, { href: '#output', title: 'Output' }, { href: '#defaults', title: 'Defaults' }, + { href: '#runner', title: 'Runner' }, { href: '#chunking', title: 'Chunking' }, { href: '#schedule-triggers', title: 'Schedule Triggers' }, { href: '#environment-variables', title: 'Environment Variables' }, @@ -233,6 +234,26 @@ model = "anthropic/claude-opus-4-5"`}

    Model precedence: trigger > skill > defaults > CLI flag (-m) > env var (WARDEN_MODEL). Most specific wins.

    Synthesis fallback: defaults.synthesis.model falls back to defaults.auxiliary.model when not set.

    +

    Runner

    + +

    Runner settings control run-level concurrency.

    + +
    +
    concurrency
    +
    Maximum concurrent file analyses across running CLI skills. In GitHub Actions, also limits matched trigger dispatch. If unset, the CLI uses 4 and the Action uses the workflow parallel input.
    +
    + + + + + +

    For CLI runs, --parallel overrides runner.concurrency. In GitHub Actions, runner.concurrency overrides the workflow parallel input.

    +

    Chunking

    Control how files are split for analysis. By default, Warden analyzes each hunk separately.

    @@ -562,7 +583,7 @@ jobs:
    fail-check
    Whether to fail the check run. Default: false
    parallel
    -
    Maximum concurrent trigger executions. Default: 5
    +
    Maximum concurrent matched trigger executions and file analyses, unless runner.concurrency is set. Default: 5
    diff --git a/src/action/workflow/pr-workflow.test.ts b/src/action/workflow/pr-workflow.test.ts index 5c7bf79f..d06972f7 100644 --- a/src/action/workflow/pr-workflow.test.ts +++ b/src/action/workflow/pr-workflow.test.ts @@ -382,6 +382,61 @@ describe('runPRWorkflow', () => { expect(semaphore).toBeInstanceOf(Semaphore); }); + it('honors the parallel input when dispatching matched triggers', async () => { + let activeRuns = 0; + let maxActiveRuns = 0; + let invocationCount = 0; + let resolveFirstRun!: () => void; + let resolveFirstRunStarted!: () => void; + const firstRun = new Promise((resolve) => { + resolveFirstRun = resolve; + }); + const firstRunStarted = new Promise((resolve) => { + resolveFirstRunStarted = resolve; + }); + + mockRunSkillTask.mockImplementation(async (taskOptions) => { + invocationCount++; + activeRuns++; + maxActiveRuns = Math.max(maxActiveRuns, activeRuns); + try { + if (invocationCount === 1) { + resolveFirstRunStarted(); + await firstRun; + } + return { + name: taskOptions.name, + report: createSkillReport({ skill: taskOptions.displayName ?? taskOptions.name }), + }; + } finally { + activeRuns--; + } + }); + + const workflow = runPRWorkflow( + mockOctokit, + createDefaultInputs({ + baseConfigPath: '.warden-org/warden.toml', + baseSkillRoot: '.warden-org', + parallel: 1, + }), + 'pull_request', + EVENT_PAYLOAD_PATH, + FIXTURES_DIR + ); + + await firstRunStarted; + // Let any incorrectly dispatched second trigger reach the mocked runner. + await new Promise((resolve) => setTimeout(resolve, 0)); + const callsBeforeFirstRunFinished = mockRunSkillTask.mock.calls.length; + resolveFirstRun(); + await workflow; + + expect(mockRunSkillTask).toHaveBeenCalledTimes(2); + expect(callsBeforeFirstRunFinished).toBe(1); + expect(maxActiveRuns).toBe(1); + }); + it('records trigger failure and updates check before failing', async () => { // When all triggers fail, the workflow should still update the check // before calling setFailed. diff --git a/src/action/workflow/pr-workflow.ts b/src/action/workflow/pr-workflow.ts index a4aba836..04d38764 100644 --- a/src/action/workflow/pr-workflow.ts +++ b/src/action/workflow/pr-workflow.ts @@ -319,15 +319,14 @@ async function executeAllTriggers( } const claudePath = usesClaudeRuntime ? await findClaudeCodeExecutable() : undefined; - // Global semaphore gates file-level work across all triggers. - // All triggers launch immediately; the semaphore limits concurrent file analyses. const semaphore = new Semaphore(concurrency); const abortController = new AbortController(); const circuitBreaker = new ProviderFailureCircuitBreaker({ abortController }); + // Limit trigger dispatch too; the semaphore only gates work after a trigger starts. return runPool( matchedTriggers, - matchedTriggers.length, + concurrency, (trigger) => executeTrigger(trigger, { octokit, diff --git a/src/cli/args.ts b/src/cli/args.ts index 5cc8855c..d5a50a78 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -21,7 +21,7 @@ export const CLIOptionsSchema = z.object({ /** Only show findings at or above this confidence in output */ minConfidence: ConfidenceThresholdSchema.optional(), help: z.boolean().default(false), - /** Max concurrent task or skill executions (default depends on command) */ + /** Max concurrent file analyses across running skills (default depends on command) */ parallel: z.number().int().positive().optional(), /** Model to use for analysis (fallback when not set in config) */ model: z.string().optional(), diff --git a/src/cli/help.ts b/src/cli/help.ts index 0910b633..04ccde25 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -121,7 +121,7 @@ const HELP_OPTIONS: Record = { }, parallel: { label: '--parallel ', - description: 'Max concurrent task or skill executions', + description: 'Max concurrent file analyses across running skills', }, failFast: { label: '-x, --fail-fast',