SDK reference: triggers
Triggers
Section titled “Triggers”Triggers define when a workflow runs. KiCI provides 23 trigger types: 16 GitHub webhook triggers and 7 internal/generic triggers for event routing, scheduling, and non-GitHub sources. Each trigger returns a frozen config object with a unique _tag discriminator.
All triggers use a config object form — pass an options object to configure the trigger.
Create a pull request trigger. Returns a frozen PrTriggerConfig directly.
function pr(config?: PrConfigInput): PrTriggerConfig;Config options:
interface PrConfigInput { events?: PrEvent[]; target?: string | RegExp | (string | RegExp)[]; source?: string | RegExp | (string | RegExp)[]; paths?: string[]; // Use '!' prefix for exclusions (e.g., '!docs/**') repos?: string | RegExp | (string | RegExp)[]; // Cross-repo source patterns -- see global-workflows.md description?: string;}PrEvent values: 'opened', 'synchronize', 'reopened', 'closed', 'assigned', 'unassigned', 'labeled', 'unlabeled', 'edited', 'converted_to_draft', 'ready_for_review', 'locked', 'unlocked', 'review_requested', 'review_request_removed', 'auto_merge_enabled', 'auto_merge_disabled'
Default events (when events is not specified): opened, synchronize, reopened, closed
Examples:
// All PRs with default eventspr();
// PRs targeting main with path filterpr({ target: 'main', events: ['opened', 'synchronize'], paths: ['src/**'] });
// Regex branch patternpr({ target: /^release\/v\d+$/ });push()
Section titled “push()”Create a push trigger. Returns a frozen PushTriggerConfig directly.
function push(config?: PushConfigInput): PushTriggerConfig;Config options:
interface PushConfigInput { branches?: string | RegExp | (string | RegExp)[]; tags?: string | RegExp | (string | RegExp)[]; paths?: string[]; // Use '!' prefix for exclusions (e.g., '!docs/**') repos?: string | RegExp | (string | RegExp)[]; // Cross-repo source patterns -- see global-workflows.md description?: string;}Examples:
// Any pushpush();
// Push to main onlypush({ branches: 'main' });
// Push with branch and path filterspush({ branches: ['main', 'develop'], paths: ['src/**'] });
// Tag pushespush({ tags: ['v*'] });Path filter behavior
Section titled “Path filter behavior”A pr() or push() trigger with paths matches the event’s changed files
against your patterns. An available list is matched exactly (an event with no
matching change — including a diff-less branch create or delete — does not run).
When the diff is unavailable (chiefly a universal-git pull-request event,
whose webhook carries no diff), path filters match conservatively so the
workflow runs rather than being silently dropped, and the delivery is recorded
as degraded. GitHub always provides an exact list; a transient API error fails
loudly, not as empty.
Create a tag trigger. Returns a frozen TagTriggerConfig.
function tag(config?: TagConfigInput): TagTriggerConfig;Config options: patterns (string/RegExp/array), description
tag(); // Any tagtag({ patterns: ['v*'] }); // Semver tagstag({ patterns: /^v\d+\.\d+$/ }); // Regex matchcomment()
Section titled “comment()”Create an issue/PR comment trigger. Returns a frozen CommentTriggerConfig.
function comment(config?: CommentConfigInput): CommentTriggerConfig;Config options: actions (created/edited/deleted), source (issue/pr), bodyMatch (string or RegExp), description
comment(); // Any commentcomment({ bodyMatch: '/deploy' }); // Glob match on bodycomment({ bodyMatch: /^\/deploy/i }); // Regex match on bodycomment({ source: 'pr', actions: ['created'] }); // PR comments onlyreview()
Section titled “review()”Create a pull request review trigger. Returns a frozen ReviewTriggerConfig.
function review(config?: ReviewConfigInput): ReviewTriggerConfig;Config options: actions (submitted/edited/dismissed), states (approved/changes_requested/commented/dismissed), description
review(); // Any reviewreview({ states: ['approved'] }); // Approvals onlyreview({ actions: ['submitted'], states: ['approved'] }); // Submitted approvalsreviewComment()
Section titled “reviewComment()”Create a PR review comment trigger. Returns a frozen ReviewCommentTriggerConfig.
function reviewComment(config?: ReviewCommentConfigInput): ReviewCommentTriggerConfig;Config options: actions (created/edited/deleted), description
reviewComment(); // Any review commentreviewComment({ actions: ['created'] }); // New review comments onlyrelease()
Section titled “release()”Create a release trigger. Returns a frozen ReleaseTriggerConfig.
function release(config?: ReleaseConfigInput): ReleaseTriggerConfig;Config options: actions (published/unpublished/created/edited/deleted/prereleased/released), description
release(); // Any release eventrelease({ actions: ['published'] }); // Published releases onlydispatch()
Section titled “dispatch()”Create a repository_dispatch trigger. Returns a frozen DispatchTriggerConfig.
function dispatch(config?: DispatchConfigInput): DispatchTriggerConfig;Config options: types (string[]), description, inputs (typed dispatch inputs map)
dispatch(); // Any dispatchdispatch({ types: ['deploy', 'rollback'] }); // Specific event typesTyped dispatch inputs
Section titled “Typed dispatch inputs”A dispatch() trigger can declare a typed inputs schema. Operators supply
values with kici run --input key=value; KiCI validates, coerces, defaults, and
exposes them to steps and rules as ctx.dispatchInputs. The values are validated
on the orchestrator from the compiled lock file — a missing required input or a
bad value is rejected before any agent runs, without cloning the repository.
import { workflow, job, step, dispatch, defineDispatchInputs, z } from '@kici-dev/sdk';
const inputs = defineDispatchInputs({ target: z.string().optional(), skipCveScan: z.boolean().default(false), skipCveScanReason: z.string().min(1).optional(), mode: z.enum(['full', 'edge-only']).default('full'), retries: z.number().int().min(0).max(10).default(3),});
export default workflow('deploy-prod', { on: dispatch({ types: ['deploy-prod'], inputs }), jobs: [ job('gates', { runsOn: 'kici:group:ops', steps: [ step('cve-gate', async (ctx) => { const i = inputs.from(ctx); // fully typed per declared key if (i.skipCveScan) { ctx.log.warn(`CVE gate skipped: ${i.skipCveScanReason ?? '(no reason)'}`); return; } await ctx.$`pnpm scan:cve:gate`; }), ], }), ],});defineDispatchInputs(map)is the single declaration site. It returns a handle thatdispatch({ inputs })accepts and exposesinputs.from(ctx)— a typed reader overctx.dispatchInputs, typed per declared key.dispatch({ inputs })also accepts a bare{ name: schema }map directly when you don’t need the reader.ctx.dispatchInputsis always present (a validated map ofstring | number | boolean | null), distinct fromctx.inputs(typed outputs fromneedsdependencies). Rules see the same values viactx.dispatchInputs, soskipUnless(ctx => !ctx.dispatchInputs.skipCveScan)works.- Defaults are applied once, on the orchestrator (the authoritative side); the
CLI pre-validates
--inputfor fast feedback and forwards the raw operator pairs.
Allowed input types (closed subset): z.string(), z.number(),
z.boolean(), z.enum([...]), z.literal(v), with the modifiers .optional(),
.nullable(), .default(v), .min(n), .max(n), .regex(re), .int().
Anything outside this set (.refine(), .transform(), .pipe(), z.object(),
z.array(), z.union(), z.record(), z.coerce.*) is a compile error —
the closed set is what guarantees the schema survives the trip to the
orchestrator’s lock file without silently dropping any validation. CLI strings
are coerced for you (--input retries=3 becomes the number 3; booleans accept
true/false/1/0/yes/no), so author your schema with clean types.
create()
Section titled “create()”Create a ref creation trigger (branches/tags). Returns a frozen CreateTriggerConfig.
function create(config?: CreateConfigInput): CreateTriggerConfig;Config options: refTypes (branch/tag), patterns (string/RegExp/array), description
create(); // Any ref creationcreate({ refTypes: ['tag'], patterns: ['v*'] }); // Tag creation onlydelete()
Section titled “delete()”Create a ref deletion trigger (branches/tags). Returns a frozen DeleteTriggerConfig.
Note: Since delete is a JavaScript reserved word, import as del: import { delete as del } from '@kici-dev/sdk'
function del(config?: DeleteConfigInput): DeleteTriggerConfig;Config options: refTypes (branch/tag), patterns (string/RegExp/array), description
del(); // Any ref deletiondel({ refTypes: ['branch'], patterns: ['temp/*'] }); // Temp branch cleanupstatus()
Section titled “status()”Create a commit status trigger. Returns a frozen StatusTriggerConfig.
function status(config?: StatusConfigInput): StatusTriggerConfig;Config options: contexts (picomatch strings like ‘ci/*’), states (error/failure/pending/success), description
status(); // Any statusstatus({ contexts: ['ci/*'], states: ['success'] }); // CI successworkflowRun()
Section titled “workflowRun()”Create a workflow_run trigger. Returns a frozen WorkflowRunTriggerConfig.
function workflowRun(config?: WorkflowRunConfigInput): WorkflowRunTriggerConfig;Config options: actions (requested/completed/in_progress), workflows (name filters), conclusions (success/failure/cancelled), description
workflowRun(); // Any workflow runworkflowRun({ workflows: ['CI'], actions: ['completed'], conclusions: ['success'] });fork()
Section titled “fork()”Create a fork trigger. No filter fields. Returns a frozen ForkTriggerConfig.
function fork(config?: ForkConfigInput): ForkTriggerConfig;fork(); // Any fork eventfork({ description: 'Track forks' }); // With descriptionstar()
Section titled “star()”Create a star trigger. Returns a frozen StarTriggerConfig.
function star(config?: StarConfigInput): StarTriggerConfig;Config options: actions (created/deleted), description
star(); // Any star eventstar({ actions: ['created'] }); // New stars onlywatch()
Section titled “watch()”Create a watch trigger. Returns a frozen WatchTriggerConfig.
function watch(config?: WatchConfigInput): WatchTriggerConfig;Config options: actions (started), description
watch(); // Any watch eventwatch({ actions: ['started'] }); // Watch started onlywebhook()
Section titled “webhook()”Create a catch-all webhook trigger for any GitHub event. Returns a frozen WebhookTriggerConfig. Unlike other triggers, events is required — catch-all must specify what to catch.
function webhook(config: WebhookConfigInput): WebhookTriggerConfig;Config options: events (required string[]), actions (optional string[]), repos (optional cross-repo source patterns — see global workflows), description
webhook({ events: ['deployment'] }); // Deployment eventswebhook({ events: ['deployment', 'deployment_status'] }); // Multiple eventswebhook({ events: ['deployment'], actions: ['created'] }); // With action filterCross-source delivery
Section titled “Cross-source delivery”A webhook() trigger fires whenever a matching event arrives via any inbound webhook source within the same org, not just the source the workflow’s repository is bound to. If your repo is registered through a github source and a separate generic source in the same org POSTs an event with a matching name, the workflow still runs.
Two important rules govern the cross-source path:
- The registration’s source owns dispatch credentials. The runtime clone, auth, and check-status posting come from the source the workflow was registered with (via its default-branch push), never from the inbound source. A generic webhook fanning out to a github-registered workflow uses the github bundle’s clone token provider — the generic source contributes only the event payload.
- Org isolation is structural. A webhook delivered to org A can never trigger a workflow registered against org B. The lookup index is keyed on
(customerId, eventName)so cross-org leakage is impossible.
The orchestrator emits kici_cross_source_fanout_size (histogram) per inbound webhook so operators can observe how many workflows each event reaches.
Event triggers
Section titled “Event triggers”The following 7 trigger types support internal event routing, scheduling, lifecycle orchestration, and non-GitHub webhook sources.
kiciEvent()
Section titled “kiciEvent()”Create a custom event trigger. Fires when a named internal event is emitted from a workflow step via ctx.emit(). Returns a frozen KiciEventTriggerConfig.
function kiciEvent(config: KiciEventConfigInput): KiciEventTriggerConfig;Config options:
interface KiciEventConfigInput { name: string; // Required: event name to listen for match?: Record<string, unknown>; // JSONPath payload matching (e.g., { '$.env': 'prod' }) not?: Record<string, unknown>; // Negative JSONPath filter source?: string; // Cross-repo source filter (e.g., 'org/infra-repo') description?: string;}kiciEvent({ name: 'deploy-complete' }); // Match by namekiciEvent({ name: 'deploy-complete', match: { '$.env': 'prod' } }); // With payload filterkiciEvent({ name: 'deploy-complete', not: { '$.env': 'staging' } }); // Negative filterkiciEvent({ name: 'deploy-complete', source: 'org/infra-repo' }); // Cross-repoworkflowComplete()
Section titled “workflowComplete()”Create a workflow completion trigger. Fires automatically when another workflow finishes execution. Returns a frozen WorkflowCompleteTriggerConfig.
function workflowComplete(config?: WorkflowCompleteConfigInput): WorkflowCompleteTriggerConfig;Config options:
interface WorkflowCompleteConfigInput { name?: string; // Filter by workflow name status?: WorkflowCompleteStatus[]; // Filter by completion status source?: string; // Cross-repo source filter description?: string;}type WorkflowCompleteStatus = 'success' | 'failed' | 'cancelled';workflowComplete(); // Any workflow completionworkflowComplete({ name: 'CI' }); // Specific workflowworkflowComplete({ name: 'CI', status: ['success'] }); // Success onlyworkflowComplete({ name: 'CI', status: ['success'], source: 'org/repo' }); // Cross-repoworkflowsFailedBatch()
Section titled “workflowsFailedBatch()”Create a batched failure trigger. Instead of firing once per failed workflow, it accumulates every failed workflow completion over a time window and fires the subscribing workflow once with the whole list — so a mass incident (a bad deploy failing hundreds of runs at once) notifies a single time, not once per failure. Returns a frozen WorkflowsFailedBatchTriggerConfig.
function workflowsFailedBatch( config: WorkflowsFailedBatchConfigInput,): WorkflowsFailedBatchTriggerConfig;Config options:
interface WorkflowsFailedBatchConfigInput { accumulateFor: number; // Accumulation window in milliseconds (opens on the first failure) name?: string; // Filter by failed workflow name source?: string; // Cross-repo source filter description?: string;}The first failure inside the window opens it; when the window closes, the subscribing workflow is dispatched once. The batch is delivered on ctx.event.payload:
// ctx.event.payload for a workflowsFailedBatch dispatch:// {// total: number, // total failures in the window// runs: Array<{ // the failed runs (bounded — the first 200)// runId: string;// repo: string;// workflowName: string;// failureClass?: string; // why the run failed// senderUsername?: string; // triggering actor, when known// }>,// }workflowsFailedBatch({ accumulateFor: 10000 }); // One notification per 10s burst of failuresworkflowsFailedBatch({ accumulateFor: 30000, name: 'CI' }); // Only CI failuresworkflowsFailedBatch({ accumulateFor: 30000, source: 'org/repo' }); // Cross-repo source filterA workflow dispatched by a failure trigger (workflowsFailedBatch, or workflowComplete({ status: ['failed'] })) never re-triggers the same batch on its own failure — a notifier that itself fails cannot loop.
jobComplete()
Section titled “jobComplete()”Create a job completion trigger. Fires automatically when a specific job within a workflow finishes. Returns a frozen JobCompleteTriggerConfig.
function jobComplete(config?: JobCompleteConfigInput): JobCompleteTriggerConfig;Config options:
interface JobCompleteConfigInput { workflow?: string; // Filter by workflow name job?: string; // Filter by job name status?: JobCompleteStatus[]; // Filter by completion status source?: string; // Cross-repo source filter description?: string;}type JobCompleteStatus = 'success' | 'failed' | 'cancelled' | 'skipped';jobComplete(); // Any job completionjobComplete({ workflow: 'CI', job: 'build' }); // Specific workflow + jobjobComplete({ workflow: 'CI', job: 'build', status: ['success'] }); // Success onlyjobComplete({ workflow: 'CI', job: 'build', source: 'org/repo' }); // Cross-repojobComplete() starts a new workflow run that reacts to another job finishing (gated on the prior job’s status). For same-run fan-out — generating follow-up jobs from a prior job’s outputs within the same run — use a result-aware dynamicJob(group, { needs, generate }) instead.
genericWebhook()
Section titled “genericWebhook()”Create a generic webhook trigger. Fires when a non-GitHub webhook is received from an external source configured via the admin API. Returns a frozen GenericWebhookTriggerConfig.
function genericWebhook(config: GenericWebhookConfigInput): GenericWebhookTriggerConfig;Config options:
interface GenericWebhookConfigInput { source: string; // Required: must match `--name` from `kici-admin source add generic` events?: string[]; // Filter by event types match?: Record<string, unknown>; // JSONPath payload matching not?: Record<string, unknown>; // Negative JSONPath filter auth?: GenericWebhookAuth; // HMAC or API key authentication path?: string; // URL path pattern (replaces source for URL matching) description?: string;}genericWebhook({ source: 'argocd' }); // Any event from ArgoCDgenericWebhook({ source: 'argocd', events: ['deploy.success'] }); // Specific eventsgenericWebhook({ source: 'argocd', match: { '$.env': 'prod' } }); // With payload filtergenericWebhook({ source: 'argocd', not: { '$.dry_run': true } }); // Negative filtergenericWebhook({ source: 'stripe', auth: { method: 'hmac-sha256', secret: 'stripe-key', signatureHeader: 'stripe-signature' },}); // HMAC authgenericWebhook({ source: 'slack', auth: { method: 'api-key', secret: 'slack-token' } }); // API key authgenericWebhook({ source: 'stripe', path: 'stripe/payments' }); // URL path patternschedule()
Section titled “schedule()”Create a cron-based schedule trigger. Returns a frozen ScheduleTriggerConfig.
function schedule(config: ScheduleConfigInput): ScheduleTriggerConfig;Config options:
interface ScheduleConfigInput { cron: string; // Required: cron expression (5-field) timezone?: string; // Timezone for cron evaluation (default: 'UTC') description?: string; // Human-readable description of the schedule inputs?: DispatchInputsMap; // Optional: defaults-only typed inputs (see below)}schedule({ cron: '0 * * * *' }); // Every hourschedule({ cron: '0 0 * * *' }); // Daily at midnight UTCschedule({ cron: '0 9 * * 1', timezone: 'America/New_York' }); // Monday 9am ETschedule({ cron: '*/15 * * * *', description: 'health check every 15 min' });A workflow may declare multiple schedule() triggers. Each schedule is
evaluated and fired independently — a Monday 9am schedule and a Friday 6pm
schedule on the same workflow both run at their own times:
on: [schedule({ cron: '0 9 * * 1' }), schedule({ cron: '0 18 * * 5' })];Schedule inputs (defaults-only)
Section titled “Schedule inputs (defaults-only)”A schedule() trigger may declare typed inputs. A cron or dashboard
“run now” fire carries no operator-supplied values, so each input resolves
from its declared default and is exposed to steps and rules as
ctx.dispatchInputs — the same surface as typed dispatch inputs.
Because there is no operator to supply a value, every schedule input must
declare a .default() or be .optional(). An input that is neither is
rejected at kici compile time.
import { workflow, job, schedule, z } from '@kici-dev/sdk';
export default workflow('nightly', { on: schedule({ cron: '0 3 * * *', inputs: { mode: z.enum(['full', 'quick']).default('full') }, }), jobs: [ job('build', { runsOn: 'default', run: async (ctx) => { ctx.log(`mode = ${ctx.dispatchInputs.mode}`); // "full" on every fire }, }), ],});You can also share a typed handle via defineDispatchInputs(...) and read it
back with .from(ctx), exactly as with dispatch(). The allowed input types
are the same closed subset documented under
typed dispatch inputs.
lifecycle()
Section titled “lifecycle()”Create a lifecycle trigger for cross-workflow orchestration events. Returns a frozen LifecycleTriggerConfig.
function lifecycle(config: LifecycleConfigInput): LifecycleTriggerConfig;Config options:
interface LifecycleConfigInput { events: LifecycleEvent[]; // Required: lifecycle events to listen for sources?: string[]; // Optional: filter by source repo (e.g., 'org/repo') description?: string; // Human-readable description}
type LifecycleEvent = 'workflow_complete' | 'job_complete' | 'job_failed' | 'registration_updated';lifecycle({ events: ['workflow_complete'] }); // Any workflow completionlifecycle({ events: ['job_failed'], sources: ['org/deploy-repo'] }); // Job failures from specific repolifecycle({ events: ['registration_updated'] }); // Workflow registration changesBranch patterns
Section titled “Branch patterns”Both pr() and push() (as well as tag(), create(), and delete()) accept glob strings and RegExp literals for pattern matching:
// Glob patterns (micromatch syntax)pr({ target: ['main', 'release/*', 'feature/**'] });
// Regex patternspr({ target: /^release\/v\d+\.\d+$/ });
// Mixedpush({ branches: ['main', /^hotfix\//] });Glob patterns use micromatch syntax. Regex patterns use standard JavaScript RegExp.