Skip to content

feat: scheduled pipelines + dependency cascade fix#80

Open
dean0x wants to merge 16 commits intomainfrom
feat/scheduled-pipelines-78
Open

feat: scheduled pipelines + dependency cascade fix#80
dean0x wants to merge 16 commits intomainfrom
feat/scheduled-pipelines-78

Conversation

@dean0x
Copy link
Owner

@dean0x dean0x commented Mar 11, 2026

Summary

  • Scheduled Pipelines: New SchedulePipeline MCP tool and --pipeline --step CLI flags for creating cron or one-time schedules that trigger multi-step pipelines (2–20 steps). Each trigger creates fresh tasks with linear dependencies.
  • Dependency Failure Cascade (breaking): Failed/cancelled upstream tasks now cascade cancellation to dependents instead of incorrectly unblocking them.
  • Queue Handler Race Condition: Fast-path dependencyState check prevents blocked tasks from being enqueued before dependency rows are written to DB.

Changes (21 files, +2180 / -183)

Bug fixes:

  • dependency-handler.ts — cascade cancellation on failed/cancelled upstream
  • queue-handler.ts — fast-path blocked task check via dependencyState

Core:

  • domain.tspipelineSteps on Schedule, ScheduledPipelineCreateRequest
  • interfaces.tscreateScheduledPipeline(), cancelTasks on cancel
  • database.ts — migration 8: pipeline_steps, pipeline_task_ids columns
  • schedule-repository.ts — JSON round-trip with Zod validation
  • schedule-manager.tscreateScheduledPipeline(), shared timing validation, cancelTasks support
  • schedule-handler.tshandlePipelineTrigger() with partial save failure cleanup

Adapters & CLI:

  • mcp-adapter.tsSchedulePipeline tool, enhanced CancelSchedule/ListSchedules/GetSchedule
  • schedule.ts--pipeline --step flags, --cancel-tasks on cancel

Tests (188 new, 1,467 total):

  • Dependency cascade (3), queue handler fast-path (2), schedule handler pipeline (5)
  • Schedule manager (5), schedule repo (4), MCP adapter (5), CLI (3)

Docs:

  • CLAUDE.md, FEATURES.md, ROADMAP.md, README.md updated for v0.6.0

Closes #78

Test plan

  • npm run test:handlers — 115 passed (dependency cascade + queue handler + schedule handler pipeline)
  • npm run test:services — 141 passed (schedule manager createScheduledPipeline)
  • npm run test:implementations — 302 passed (schedule repo pipeline round-trip)
  • npm run test:adapters — 55 passed (SchedulePipeline tool, CancelSchedule cancelTasks)
  • npm run test:cli — 150 passed (--pipeline --step, --cancel-tasks)
  • npm run test:all — 1,467 tests, zero regressions
  • npm run build — clean

Dean Sharon added 2 commits March 11, 2026 16:06
Add SchedulePipeline MCP tool and CLI support for creating recurring or
one-time schedules that trigger multi-step pipelines. Each trigger creates
fresh tasks with linear dependencies (step N depends on step N-1).

Bug fixes:
- Dependency failure cascade: failed/cancelled upstream tasks now cascade
  cancellation to dependents instead of incorrectly unblocking them
- Queue handler race condition: fast-path dependencyState check prevents
  blocked tasks from being enqueued before dependency rows are written

Features:
- SchedulePipeline MCP tool (2-20 steps, cron/one-time, per-step agent)
- CLI: --pipeline --step flags for schedule create
- CancelSchedule: optional cancelTasks flag for in-flight pipeline tasks
- ListSchedules: isPipeline/stepCount indicators
- GetSchedule: pipelineSteps in response
- Database migration 8: pipeline_steps, pipeline_task_ids columns
- 188 new tests (1,467 total), zero regressions
@greptile-apps
Copy link

greptile-apps bot commented Mar 11, 2026

Confidence Score: 4/5

  • PR is safe to merge; the one identified bug is a CLI UX issue (silent discard of --step without --pipeline) with no data-loss risk.
  • Architecture is sound, breaking change is well-documented, 188 new tests cover the critical paths (cascade, queue fast-path, pipeline trigger, repo round-trip, MCP adapter, CLI), and zero regressions. Score reduced by one point for the --step/--pipeline UX bug which could cause user confusion in production CLI usage.
  • src/cli/commands/schedule.ts — --step without --pipeline silently drops steps

Important Files Changed

Filename Overview
src/services/handlers/schedule-handler.ts Major refactor: single-task trigger extracted to handleSingleTaskTrigger; new handlePipelineTrigger creates N tasks with linear dependencies, resolves afterScheduleId onto step 0, records execution with lastTaskId (fixing the chaining issue). Shared helpers (resolveAfterScheduleTaskId, recordTriggeredExecution, recordFailedExecution, updateScheduleAfterTrigger) cleanly decompose the old monolithic handler. Step-0 TaskDelegated failure now cancels all saved tasks before returning the error.
src/services/handlers/dependency-handler.ts Adds cascade-cancellation: after a task becomes unblocked, getDependencies is called to check for failed/cancelled resolutions; if any exist, TaskCancellationRequested is emitted instead of TaskUnblocked. getDependencies failure now skips unblocking with a warning (conservative path), fixing the earlier gap.
src/services/handlers/queue-handler.ts Fast-path added: if task.dependencyState === 'blocked' at TaskPersisted time, skip the DB isBlocked() check entirely, eliminating the race condition where dependency rows weren't written yet. Small, surgical fix with clear rationale.
src/services/schedule-manager.ts New createScheduledPipeline method with per-step path normalization (uses normalizedSteps correctly), shared timing validation extracted to validateScheduleTiming. cancelSchedule extended with cancelTasks flag that cancels in-flight tasks from latest execution only.
src/adapters/mcp-adapter.ts New SchedulePipeline tool with full Zod validation and JSON schema; CancelSchedule extended with cancelTasks; ListSchedules adds isPipeline/stepCount indicators; GetSchedule adds pipelineSteps to response. Response fields are clearly named.
src/cli/commands/schedule.ts New --pipeline/--step flags and --cancel-tasks support added. A usability bug exists: --step flags provided without --pipeline are silently discarded with no warning, which can confuse users who omit --pipeline by accident.
src/implementations/schedule-repository.ts pipeline_steps and pipeline_task_ids columns added with full Zod boundary validation on read. PipelineStepsSchema enforces min(2)/max(20). pipeline_task_ids parse failure is non-fatal (logs and returns undefined), which is appropriate since the field is informational.
src/core/domain.ts ScheduledPipelineCreateRequest type added; pipelineSteps added to Schedule and ScheduleRequest interfaces. Clean, minimal changes.
src/implementations/database.ts Migration 8 adds nullable pipeline_steps TEXT to schedules and pipeline_task_ids TEXT to schedule_executions. Standard migration pattern, no issues.

Sequence Diagram

sequenceDiagram
    participant SE as ScheduleExecutor
    participant SH as ScheduleHandler
    participant TR as TaskRepo
    participant SR as ScheduleRepo
    participant EB as EventBus
    participant DH as DependencyHandler
    participant QH as QueueHandler

    SE->>EB: emit(ScheduleTriggered)
    EB->>SH: handleScheduleTriggered()
    SH->>SH: resolveAfterScheduleTaskId()
    SH->>SH: handlePipelineTrigger(schedule, steps)

    loop For each step i (0..N-1)
        SH->>TR: save(task_i, dependsOn=[task_{i-1}])
        TR-->>SH: ok(task_i)
    end

    SH->>SR: recordExecution(lastTaskId, allTaskIds)
    SH->>SR: updateSchedule(runCount, nextRunAt)

    loop TaskDelegated for each step
        SH->>EB: emit(TaskDelegated, task_i)
        EB->>DH: handleTaskDelegated()
        Note over DH: Register dependency rows for step i
        EB->>QH: handleTaskPersisted()
        alt task.dependencyState === blocked
            QH-->>EB: skip enqueue (fast-path)
        else not blocked (step 0)
            QH->>EB: emit(TaskEnqueued)
        end
    end

    SH->>EB: emit(ScheduleExecuted, lastTaskId)

    Note over DH: When step i completes...
    DH->>DH: getDependencies(step_{i+1})
    alt all deps completed
        DH->>EB: emit(TaskUnblocked, step_{i+1})
        QH->>EB: emit(TaskEnqueued, step_{i+1})
    else any dep failed/cancelled
        DH->>EB: emit(TaskCancellationRequested, step_{i+1})
        Note over DH: Cascade continues recursively
    end
Loading

Last reviewed commit: 209c3dc

this.logger.info('Injected afterSchedule dependency on pipeline step 0', {
scheduleId,
afterScheduleId: schedule.afterScheduleId,
dependsOnTaskId: latestExecution.taskId,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH: Duplicated afterScheduleId resolution logic

The handlePipelineTrigger method contains inline afterScheduleId resolution (lines 327-345 approximately) that duplicates the extracted resolveAfterScheduleDependency helper (lines 455-483). The single-task path correctly calls the shared helper, but the pipeline path re-implements the same business logic:

  • Fetch execution history
  • Get latest execution
  • Check if task exists and is in terminal state
  • Return undefined or the task ID

Impact: Two places to maintain the same logic. If afterScheduleId resolution rules change (e.g., checking multiple executions, different terminal state semantics), both paths must be updated independently.

Fix: Refactor resolveAfterScheduleDependency to return the resolved TaskId | undefined instead of a modified template, so both handleSingleTaskTrigger and handlePipelineTrigger consume the same primitive:

private async resolveAfterScheduleTaskId(afterScheduleId: ScheduleId): Promise<TaskId | undefined> {
  const historyResult = await this.scheduleRepo.getExecutionHistory(afterScheduleId, 1);
  if (!historyResult.ok || historyResult.value.length === 0) return undefined;
  const latestExecution = historyResult.value[0];
  if (!latestExecution.taskId) return undefined;
  const depTaskResult = await this.taskRepo.findById(latestExecution.taskId);
  if (!depTaskResult.ok || !depTaskResult.value || isTerminalState(depTaskResult.value.status)) {
    return undefined;
  }
  return latestExecution.taskId;
}

Flagged by: Architecture, Complexity, Consistency reviews

@dean0x
Copy link
Owner Author

dean0x commented Mar 11, 2026

Code Review Summary: PR #80 - Scheduled Pipelines

Status: CHANGES_REQUESTED

This PR introduces scheduled pipelines with dependency failure cascade fixes. The feature is well-structured overall, but there are 8 HIGH severity and 5 MEDIUM severity blocking issues that should be addressed before merge. Below is a deduplicated summary based on comprehensive reviews from 10 reviewers (Architecture, Complexity, Consistency, Database, Documentation, Performance, Regression, Security, Tests, TypeScript).


BLOCKING ISSUES

Critical

1. TASK-DEPENDENCIES.md contradicts cascade behavior (docs/TASK-DEPENDENCIES.md:468-524)

  • Status: Documentation explicitly contradicts code implementation
  • Impact: Users relying on documented behavior (failed deps unblock downstream tasks) will be surprised by automatic cancellation
  • Fix: Update documentation sections:
    • "Handle Dependency Failures" (line 466+): Replace v0.3.0 behavior with v0.6.0 cascade semantics
    • "Error Handling" example (line 377-403): Update to show cascade cancellation
    • "Cancelled Dependency Propagation" (line 554-585): Remove "does NOT automatically cancel dependents" claim
    • "Design Rationale" (line 526-538): Update since behavior changed
    • "Future Consideration (v0.4.0)" (line 539-552): Mark as implemented

HIGH Issues

2. Duplicated afterScheduleId resolution logic (src/services/handlers/schedule-handler.ts:327-345)

  • Files: schedule-handler.ts:327-345 (pipeline path) vs schedule-handler.ts:455-483 (single-task helper)
  • Reviewers: Architecture, Complexity, Consistency
  • Status: CONFIRMED - Multiple reviewers flagged the same duplication
  • Impact: Two places to maintain same business logic; divergence risk if rules change
  • Fix: Refactor resolveAfterScheduleDependency to return TaskId | undefined instead of modified template
private async resolveAfterScheduleTaskId(afterScheduleId: ScheduleId): Promise<TaskId | undefined> {
  const historyResult = await this.scheduleRepo.getExecutionHistory(afterScheduleId, 1);
  if (!historyResult.ok || historyResult.value.length === 0) return undefined;
  const latestExecution = historyResult.value[0];
  if (!latestExecution.taskId) return undefined;
  const depTaskResult = await this.taskRepo.findById(latestExecution.taskId);
  if (!depTaskResult.ok || !depTaskResult.value || isTerminalState(depTaskResult.value.status)) return undefined;
  return latestExecution.taskId;
}

3. createSchedule not using validateScheduleTiming (src/services/schedule-manager.ts:64-155 vs lines 491+)

  • Files: schedule-manager.ts
  • Reviewers: Architecture, Complexity, Consistency
  • Status: CONFIRMED - Multiple reviewers flagged the same duplication
  • Impact: ~80 lines of validation logic duplicated; JSDoc claims helper is "shared between createSchedule and createScheduledPipeline" but createSchedule was never refactored
  • Fix: Call validateScheduleTiming from createSchedule to consolidate validation logic

4. Pipeline task creation loop not wrapped in transaction (src/services/handlers/schedule-handler.ts:340-399)

  • File: schedule-handler.ts
  • Reviewer: Database
  • Impact: Process crash mid-loop leaves orphaned tasks and unresolved dependency rows; partial pipeline state with no recovery mechanism
  • Fix: Wrap entire task creation loop in a database transaction:
const txResult = await this.taskRepo.transaction(async (txRepo) => {
  for (let i = 0; i < steps.length; i++) {
    // ... build task ...
    const saveResult = await txRepo.save(task);
    if (!saveResult.ok) return saveResult;
    savedTasks.push(task);
  }
  return ok(undefined);
});
if (!txResult.ok) {
  await this.recordFailedExecution(...);
  return txResult;
}

5. MCP adapter tests use simulate helpers instead of real code (tests/unit/adapters/mcp-adapter.test.ts:857-990)

  • File: mcp-adapter.test.ts
  • Reviewer: Tests
  • Impact: ZERO integration coverage of new adapter code paths (handleSchedulePipeline, enhanced handleCancelSchedule, handleListSchedules, handleGetSchedule)
  • Fix: Replace helper calls with actual adapter.handleToolCall('SchedulePipeline', ...) to exercise real adapter methods

6. Missing release notes for v0.6.0 (docs/releases/)

  • File: docs/releases/
  • Reviewer: Documentation
  • Impact: Release workflow will fail validation since release notes file is required
  • Fix: Create docs/releases/RELEASE_NOTES_v0.6.0.md with: SchedulePipeline MCP tool, CLI flags, dependency cascade fix, queue handler race condition fix, cancelTasks, migration 8 schema changes, breaking change note

7. No JSDoc on cancelSchedule updated signature (src/services/schedule-manager.ts:238, src/core/interfaces.ts:408)

  • Files: schedule-manager.ts, interfaces.ts
  • Reviewer: Documentation
  • Impact: Third parameter cancelTasks is undocumented; callers don't know behavior
  • Fix: Add JSDoc documenting the new parameter
/**
 * @param cancelTasks - If true, also cancel in-flight tasks from the latest execution
 */

8. No JSDoc on createScheduledPipeline (src/services/schedule-manager.ts:325, src/core/interfaces.ts:410)

  • Files: schedule-manager.ts, interfaces.ts
  • Reviewer: Documentation
  • Impact: New public API undocumented; no mention of validation constraints (2-20 steps, path validation, agent resolution), error conditions
  • Fix: Add comprehensive JSDoc covering behavior, constraints, errors

MEDIUM Issues

9. Pipeline cleanup bypasses event system (src/services/handlers/schedule-handler.ts:380-387)

  • File: schedule-handler.ts
  • Reviewer: Architecture
  • Impact: Direct DB mutation skips dependency resolution and audit logging; orphaned dependency rows remain in pending state
  • Fix: Either wrap task creation in transaction (fixes this too) OR emit TaskCancellationRequested events and document architectural exception

10. Validation duplication in CLI (src/cli/commands/schedule.ts:172-175 and 222-228)

  • File: cli/commands/schedule.ts
  • Reviewer: Complexity
  • Impact: Duplicated missedRunPolicy ternary chain within same function
  • Fix: Extract to helper or reuse existing toMissedRunPolicy from schedule-manager.ts

11. Non-null assertion on pipelineSteps (src/services/handlers/schedule-handler.ts:319)

  • File: schedule-handler.ts
  • Reviewer: TypeScript
  • Impact: const steps = schedule.pipelineSteps!; bypasses type narrowing; no compile-time safety if called from different path
  • Fix: Accept steps as parameter or add local type guard
// Option A: Accept as parameter
private async handlePipelineTrigger(
  schedule: Schedule,
  triggeredAt: number,
  steps: readonly PipelineStepRequest[]
): Promise<Result<void>> { ... }

// Option B: Local guard
if (!schedule.pipelineSteps || schedule.pipelineSteps.length === 0) {
  return err(new BackbeatError(ErrorCode.INVALID_INPUT, 'Pipeline requires steps'));
}
const steps = schedule.pipelineSteps; // TypeScript narrows correctly

12. Missing Zod validation on pipeline_task_ids JSON parse (src/implementations/schedule-repository.ts:540)

  • File: schedule-repository.ts
  • Reviewers: Database, Security, TypeScript
  • Impact: Inconsistent with pipeline_steps validation; bare type assertion on untrusted DB data creates silent type corruption risk
  • Fix: Add PipelineTaskIdsSchema and use .parse() like pipeline_steps:
const PipelineTaskIdsSchema = z.array(z.string().min(1));

if (data.pipeline_task_ids) {
  try {
    const parsed = JSON.parse(data.pipeline_task_ids);
    const validated = PipelineTaskIdsSchema.parse(parsed);
    pipelineTaskIds = validated.map((id) => TaskId(id));
  } catch {
    pipelineTaskIds = undefined;
  }
}

13. No service-level test for cancelSchedule with cancelTasks=true (tests/unit/services/schedule-manager.test.ts:358-384)

  • File: schedule-manager.test.ts
  • Reviewer: Tests
  • Impact: Most complex new path in cancelSchedule (execution history lookup, pipelineTaskIds extraction, event emission) is untested at service layer
  • Fix: Add service-level tests for:
    • Pipeline task cancellation when cancelTasks=true
    • Fallback to taskId when pipelineTaskIds absent
    • Proper event emission for each task

RECOMMENDATION

CHANGES_REQUESTED - The blocking issues should be resolved before merge:

Priority 1 (Critical/High - Required):

  1. Update TASK-DEPENDENCIES.md to match actual behavior
  2. Fix duplicated afterScheduleId logic (DRY violation)
  3. Fix duplicated validateScheduleTiming logic (DRY violation)
  4. Wrap pipeline task creation in transaction (data integrity)
  5. Rewrite MCP adapter tests to use real adapter (test coverage)
  6. Create RELEASE_NOTES_v0.6.0.md (release blocker)
  7. Add JSDoc for public API surface

Priority 2 (Medium - Should Fix):
8. Add Zod validation for pipeline_task_ids (type safety)
9. Add service-level test for cancelTasks=true (coverage)
10. Fix TypeScript non-null assertions (type safety)
11. Fix CLI validation duplication (code quality)
12. Document pipeline cleanup architectural exception (maintainability)


POSITIVE OBSERVATIONS

  • Well-structured decomposition of handleScheduleTriggered into handleSingleTaskTrigger, handlePipelineTrigger, and shared helpers
  • Clean domain model extension (pipelineSteps on Schedule, pipelineTaskIds on ScheduleExecution)
  • Targeted, well-placed dependency cascade fix in DependencyHandler
  • Strong Zod validation at MCP boundary (step counts 2-20, path validation, agent enum)
  • SQL injection prevention via parameterized statements throughout
  • Immutable data patterns, Result types, proper error handling
  • Comprehensive test coverage for pipeline trigger logic, dependency cascade, queue fast-path

Files Requiring Changes

  • docs/TASK-DEPENDENCIES.md - Update cascade behavior documentation
  • docs/releases/RELEASE_NOTES_v0.6.0.md - Create new file
  • src/services/schedule-manager.ts - Refactor createSchedule to use validateScheduleTiming
  • src/services/handlers/schedule-handler.ts - Fix afterScheduleId duplication, wrap task creation in transaction, remove non-null assertion
  • src/implementations/schedule-repository.ts - Add Zod validation for pipeline_task_ids
  • src/cli/commands/schedule.ts - Remove duplicated missedRunPolicy ternary
  • src/core/interfaces.ts - Add JSDoc for cancelSchedule and createScheduledPipeline
  • tests/unit/adapters/mcp-adapter.test.ts - Rewrite tests to use real adapter
  • tests/unit/services/schedule-manager.test.ts - Add cancelTasks=true service-level tests

Claude Code - Code Review for Backbeat PR #80

Dean Sharon and others added 7 commits March 11, 2026 17:06
…k in handleSchedulePipeline

The handleSchedulePipeline handler used undefined as the nextRunAt
fallback, while all other schedule handlers (handleScheduleTask,
handleListSchedules, handleGetSchedule) used null. This caused
inconsistent JSON serialization — undefined omits the field entirely,
while null includes it explicitly as "nextRunAt": null.

Co-Authored-By: Claude <noreply@anthropic.com>
…hedule

Replace ~80 lines of inline timing validation in createSchedule with a
call to the existing validateScheduleTiming helper. The helper's JSDoc
already claims it is shared between createSchedule and
createScheduledPipeline, but createSchedule was never refactored to
use it. Now both methods follow the same pattern.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace bare `as string[]` type assertion with PipelineTaskIdsSchema
Zod validation, matching the pattern used by pipeline_steps. This
ensures malformed JSON (e.g., non-string elements, empty strings)
is caught at the database boundary rather than silently propagated.

Co-Authored-By: Claude <noreply@anthropic.com>
…lease notes

TASK-DEPENDENCIES.md contradicted running code: the v0.6.0 cascade
cancellation behavior (failed/cancelled deps auto-cancel dependents)
was not reflected in the Event Flow diagram, Error Handling examples,
or Best Practices section. Updated all sections to match the actual
DependencyHandler implementation.

Created missing RELEASE_NOTES_v0.6.0.md covering scheduled pipelines,
dependency cascade fix, queue handler race condition fix, migration 8,
SchedulePipeline MCP tool, and CLI pipeline flags.

Co-Authored-By: Claude <noreply@anthropic.com>
…type-safety

- Extract resolveAfterScheduleTaskId() returning TaskId | undefined,
  replacing the old resolveAfterScheduleDependency() that returned a
  modified task template. Both single-task and pipeline paths now call
  the same shared helper, eliminating ~18 lines of duplicated logic.
- Pass pipelineSteps as a typed parameter (NonNullable) to
  handlePipelineTrigger, removing the non-null assertion (!).
- Add ARCHITECTURE EXCEPTION comment to pipeline cleanup code explaining
  why direct taskRepo.update() is correct (no events emitted yet).
- Add TODO for async transaction wrapping (better-sqlite3 limitation).

Co-Authored-By: Claude <noreply@anthropic.com>
…rage

Add 3 tests for the cancelTasks branch in ScheduleManagerService.cancelSchedule():
- Pipeline execution with pipelineTaskIds emits TaskCancellationRequested per task
- Single taskId fallback when no pipelineTaskIds present
- cancelTasks=false does not emit TaskCancellationRequested

Co-Authored-By: Claude <noreply@anthropic.com>
Comment on lines +181 to +204
if (cancelTasks) {
const historyResult = await this.scheduleRepository.getExecutionHistory(scheduleId, 1);
if (historyResult.ok && historyResult.value.length > 0) {
const latestExecution = historyResult.value[0];
const taskIds = latestExecution.pipelineTaskIds ?? (latestExecution.taskId ? [latestExecution.taskId] : []);
for (const taskId of taskIds) {
const cancelResult = await this.eventBus.emit('TaskCancellationRequested', {
taskId,
reason: `Schedule ${scheduleId} cancelled`,
});
if (!cancelResult.ok) {
this.logger.warn('Failed to cancel pipeline task', {
taskId,
scheduleId,
error: cancelResult.error.message,
});
}
}
this.logger.info('Cancelled in-flight pipeline tasks', {
scheduleId,
taskCount: taskIds.length,
});
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancelTasks only covers the single latest execution

getExecutionHistory(scheduleId, 1) limits the lookup to one record. For CRON schedules with short intervals and slow multi-step pipelines, it is possible for a second trigger to fire before the first pipeline finishes. In that situation --cancel-tasks / cancelTasks: true will cancel the newly-created tasks from the latest run but leave the still-running tasks from the previous run untouched.

Consider fetching a small window (e.g., the last 5 executions) and deduplicating task IDs before cancelling:

const historyResult = await this.scheduleRepository.getExecutionHistory(scheduleId, 5);
if (historyResult.ok) {
  const taskIdSet = new Set<TaskId>();
  for (const execution of historyResult.value) {
    if (execution.pipelineTaskIds) {
      execution.pipelineTaskIds.forEach((id) => taskIdSet.add(id));
    } else if (execution.taskId) {
      taskIdSet.add(execution.taskId);
    }
  }
  for (const taskId of taskIdSet) { ... }
}

At a minimum the doc-comment / tool description should mention that only the most recent execution is targeted.

Dean Sharon added 5 commits March 11, 2026 22:52
Chained schedules (afterScheduleId) resolve the predecessor's
execution.taskId to check if it's terminal. Storing firstTaskId
caused the chain to fire when step 1 completed, not the full
pipeline. Now stores lastTaskId so chaining waits for pipeline
completion.
…ascade check

When getDependencies returned an error, code fell through to the
unblock path — potentially unblocking a task whose dependency
actually failed. Now logs a warning and skips the task instead.
…ectory

validatePath() resolves relative/symlink paths to absolute, but the
validation loop discarded the result — schedule stored the original
un-normalized path. Now builds normalizedSteps array with resolved
paths from validatePath().
…iledExecution

Helper hardcoded "Failed to create task: " prefix. Pipeline callsite
already passed "Pipeline failed at step N: ..." — resulting in
double-wrapping in the audit trail. Moved prefix to single-task
callsite, helper now passes errorMessage directly.
Field name implied task IDs but carried a boolean. Renamed to
cancelTasksRequested to accurately describe its semantics.
…ernary

Replace two identical 7-line ternary chains in schedule create (single-task
and pipeline modes) with the existing toMissedRunPolicy() from schedule-manager.
Guards with undefined check to preserve CLI pass-through semantics.
…eline error

Step 0 is the only task that becomes runnable — all later steps block on it.
If its TaskDelegated emit fails, the pipeline is orphaned forever. Now cancels
all saved tasks instead of continuing best-effort.

Also warn when positional prompt words are silently ignored in --pipeline mode,
and fix Biome formatting in schedule-manager test.
Comment on lines +131 to +133
} else if (arg === '--step' && next) {
pipelineSteps.push(next);
i++;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--step without --pipeline silently discards all steps

pipelineSteps is populated whenever --step appears, but is only consumed in the isPipeline branch. In single-task mode the array is never checked. A user who accidentally omits --pipeline ends up with a single-task schedule and no indication that the --step flags were ignored.

For example:

beat schedule create "run ci" --step "lint" --step "test" --cron "0 9 * * *"
# Creates a single-task schedule for "run ci"; lint/test steps are silently dropped

Add a guard before entering single-task mode:

Suggested change
} else if (arg === '--step' && next) {
pipelineSteps.push(next);
i++;
} else if (arg === '--step' && next) {
pipelineSteps.push(next);
i++;
} else if (arg.startsWith('-')) {

And after the isPipeline block returns, before the single-task prompt check:

if (pipelineSteps.length > 0) {
  ui.error('--step requires --pipeline. Did you mean: beat schedule create --pipeline --step "..." --step "..."');
  process.exit(1);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: scheduled pipelines — run multi-step DAGs on cron/one-time triggers

1 participant