Skip to content

feat(agents-core): add missing refs to runtime tables#2688

Merged
miles-kt-inkeep merged 7 commits intomainfrom
feat/add-runtime-refs
Mar 18, 2026
Merged

feat(agents-core): add missing refs to runtime tables#2688
miles-kt-inkeep merged 7 commits intomainfrom
feat/add-runtime-refs

Conversation

@miles-kt-inkeep
Copy link
Contributor

@miles-kt-inkeep miles-kt-inkeep commented Mar 13, 2026

Feature:

  • Adds ref fields to triggerInvocations, scheduledTriggerInvocations, datasetRun and evaluation run. This will enable branch tracking once we enable creating triggers on branches. Work app tables will not included a ref because they will only be edited on the main branch. The remaining tables inherit the ref by foreign key.

Future Work:

  • Modify scheduled triggers to run on branches by adding ref to ScheduledTriggerRunnerPayload.
  • Add ref param to generated webhook url if created on a branch.

@changeset-bot
Copy link

changeset-bot bot commented Mar 13, 2026

🦋 Changeset detected

Latest commit: 1cd775e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@inkeep/agents-api Patch
@inkeep/agents-core Patch
@inkeep/agents-manage-ui Patch
@inkeep/agents-cli Patch
@inkeep/agents-sdk Patch
@inkeep/agents-work-apps Patch
@inkeep/ai-sdk-provider Patch
@inkeep/create-agents Patch
@inkeep/agents-email Patch
@inkeep/agents-mcp Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Mar 17, 2026 4:57pm
agents-docs Ready Ready Preview, Comment Mar 17, 2026 4:57pm
agents-manage-ui Ready Ready Preview, Comment Mar 17, 2026 4:57pm

Request Review

Copy link
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

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

Solid, well-scoped change. The schema additions, cascade-delete logic, and ref propagation are all consistent with existing patterns. One substantive concern about silent ref resolution failure in the workflow step, and a couple of non-blocking observations below.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow runpullfrog.com𝕏

Comment on lines 282 to +293
@@ -287,6 +290,7 @@ export async function createInvocationIdempotentStep(params: {
projectId: params.projectId,
agentId: params.agentId,
scheduledTriggerId: params.scheduledTriggerId,
ref: resolvedRef ?? undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

The removed code in scheduledTriggers.ts threw an error when resolveRef returned null (Failed to resolve ref for project). Here the null is silently coerced to undefined and stored as null in the DB. The sibling step checkTriggerEnabledStep (line ~172) handles null ref by returning shouldContinue: false — consider adding a similar guard or at minimum logging a warning so failed ref resolution doesn't go unnoticed in scheduled workflows.

ALTER TABLE "dataset_run" ADD COLUMN "ref" jsonb;--> statement-breakpoint
ALTER TABLE "evaluation_run" ADD COLUMN "ref" jsonb;--> statement-breakpoint
ALTER TABLE "scheduled_trigger_invocations" ADD COLUMN "ref" jsonb;--> statement-breakpoint
ALTER TABLE "trigger_invocations" ADD COLUMN "ref" jsonb; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: missing trailing newline.

Comment on lines +106 to +107
sql`${triggerInvocations.ref}->>'name' = ${fullBranchName}`
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Pre-existing gap (non-blocking): all cascade-delete functions filter on ref->>'name' but no table has an index on the ref jsonb column or an expression index on (ref->>'name'). As table sizes grow, these DELETEs will scan the full tenant+project partition. Worth a follow-up to add expression indexes.

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(2) Total Issues | Risk: Medium

🟠⚠️ Major (1) 🟠⚠️

🟠 1) cascadeDelete.test.ts Missing test coverage for new cascade-delete tables

files:

  • packages/agents-core/src/__tests__/data-access/cascadeDelete.test.ts

Issue: The cascade-delete tests do not cover the 4 newly added tables (triggerInvocations, scheduledTriggerInvocations, datasetRun, evaluationRun). The test file imports and cleans up existing tables but does not include the 4 new tables. The cascadeDeleteByBranch, cascadeDeleteByProject, and cascadeDeleteByAgent tests only verify deletion counts for pre-existing tables, not the new triggerInvocationsDeleted, scheduledTriggerInvocationsDeleted, datasetRunsDeleted, or evaluationRunsDeleted fields.

Why: Without test coverage for the new cascade delete behavior, regressions in branch-based deletion of these tables would go undetected. A bug in the SQL query (e.g., wrong column reference in the JSON path like ref->>'name') could leave orphaned records when branches are deleted, causing data inconsistency. The cascade-delete logic is critical infrastructure that affects data integrity across the evaluation and trigger subsystems.

Fix: Add test coverage for the new tables in the existing test describe blocks:

// 1. Add imports for the new tables
import { triggerInvocations, scheduledTriggerInvocations, datasetRun, evaluationRun } from '../../db/runtime/runtime-schema';

// 2. Add cleanup in beforeEach
await db.delete(triggerInvocations);
await db.delete(scheduledTriggerInvocations);
await db.delete(datasetRun);
await db.delete(evaluationRun);

// 3. Create test data with branch refs
await db.insert(triggerInvocations).values({
  tenantId, projectId, agentId, id: generateId(),
  triggerId: 'trigger-1', ref: branch1Ref,
  status: 'pending', requestPayload: {},
});
await db.insert(datasetRun).values({
  tenantId, projectId, id: generateId(),
  datasetId: 'dataset-1', ref: branch1Ref,
});
// ... similar for scheduledTriggerInvocations and evaluationRun

// 4. Add assertions
expect(result.triggerInvocationsDeleted).toBe(1);
expect(result.scheduledTriggerInvocationsDeleted).toBe(1);
expect(result.datasetRunsDeleted).toBe(1);
expect(result.evaluationRunsDeleted).toBe(1);

Refs:

🟡 Minor (1) 🟡

Inline Comments:

  • 🟡 Minor: scheduledTriggerSteps.ts:282-283 Inconsistent null handling for ref resolution vs checkTriggerEnabledStep

💭 Consider (1) 💭

Inline Comments:

  • 💭 Consider: cascade-delete.ts:387-414 Add clarifying comment for scope asymmetry between agent-scoped triggers and project-scoped evals

🚫 REQUEST CHANGES

Summary: This PR correctly adds ref columns to 4 runtime tables following established patterns. The schema changes, migration, and cascade-delete logic are well-implemented. However, the cascade-delete tests need to be updated to cover the new tables before merging. The test coverage gap is the primary blocker. The inline comments about null handling inconsistency and documentation are minor improvements to consider.

Discarded (4)
Location Issue Reason Discarded
0023_bumpy_vampiro.sql Migration safety INFO-level only - migration is safe, additive, and follows best practices
cascade-delete.ts:446-458 datasetRuns/evaluationRuns hardcoded to 0 in cascadeDeleteByAgent Intentional design - these tables are project-scoped, not agent-scoped per schema
runtime-schema.ts Type safety of nullable JSONB columns Type design is correct and consistent with existing patterns
0023_bumpy_vampiro.sql:4 Missing trailing newline Consistent with drizzle-kit generation pattern in this codebase
Reviewers (6)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-tests 4 1 0 0 0 0 0
pr-review-precision 2 0 0 0 2 0 0
pr-review-standards 1 0 0 0 0 0 1
pr-review-consistency 5 0 0 0 0 0 5
pr-review-breaking-changes 1 0 0 0 0 0 1
pr-review-types 0 0 0 0 0 0 0
Total 13 1 0 0 2 0 7

Note: pr-review-types found no issues - nullable JSONB typing is correct and consistent.

Comment on lines +282 to +283
const ref = getProjectScopedRef(params.tenantId, params.projectId, 'main');
const resolvedRef = await resolveRef(manageDbClient)(ref);
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Minor: Inconsistent null handling for ref resolution

Issue: When resolveRef returns null, this code silently continues with undefined, creating a record without branch tracking. However, checkTriggerEnabledStep (line 174) treats the same scenario as a hard failure: 'Failed to resolve ref for project, treating trigger as deleted'.

Why: Invocations created without refs cannot be cleaned up by the branch-scoped cascade delete logic that this PR enables. This creates potential orphan records.

Fix: Consider either:

  1. Failing early when resolvedRef is null (consistent with checkTriggerEnabledStep)
  2. Adding a comment documenting that invocations without refs will require manual cleanup

Refs:

Comment on lines 387 to +414
.returning();
tasksDeleted = tasksResult.length;

// Delete trigger invocations for this agent on this branch
const triggerInvocationsResult = await db
.delete(triggerInvocations)
.where(
and(
eq(triggerInvocations.tenantId, scopes.tenantId),
eq(triggerInvocations.projectId, scopes.projectId),
eq(triggerInvocations.agentId, scopes.agentId),
sql`${triggerInvocations.ref}->>'name' = ${fullBranchName}`
)
)
.returning();

// Delete scheduled trigger invocations for this agent on this branch
const scheduledTriggerInvocationsResult = await db
.delete(scheduledTriggerInvocations)
.where(
and(
eq(scheduledTriggerInvocations.tenantId, scopes.tenantId),
eq(scheduledTriggerInvocations.projectId, scopes.projectId),
eq(scheduledTriggerInvocations.agentId, scopes.agentId),
sql`${scheduledTriggerInvocations.ref}->>'name' = ${fullBranchName}`
)
)
.returning();
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 Consider: Add clarifying comment for scope asymmetry

Issue: The function deletes triggerInvocations and scheduledTriggerInvocations by agentId, but returns hardcoded 0 for datasetRunsDeleted and evaluationRunsDeleted. This is correct (those tables are project-scoped, not agent-scoped), but the asymmetry may confuse future maintainers.

Why: The behavior is intentional since datasetRun and evaluationRun are projectScoped in the schema, while trigger invocations are agentScoped. A comment would make this explicit.

Fix: Consider adding a brief comment:

// Delete trigger invocations for this agent on this branch
// Note: datasetRun and evaluationRun are project-scoped (not agent-scoped),
// so they're handled by cascadeDeleteByProject, not here

Refs:

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(1) Total Issues | Risk: Low

🟡 Minor (1) 🟡

Inline Comments:

  • 🟡 Minor: scheduledTriggerSteps.ts:293 Inconsistent null handling for ref resolution vs checkTriggerEnabledStep

💭 Consider (1) 💭

💭 1) cascadeDelete.test.ts Add explicit zero-count assertions in cascadeDeleteBySubAgent tests

Issue: The cascadeDeleteBySubAgent tests don't assert the expected zero counts for triggerInvocationsDeleted, scheduledTriggerInvocationsDeleted, datasetRunsDeleted, and evaluationRunsDeleted.

Why: While the implementation correctly returns 0 (since sub-agents don't own triggers or evaluation runs), explicitly asserting these values documents the intentional behavior and protects against accidental future changes.

Fix: After expect(result.tasksDeleted).toBe(1), add:

expect(result.triggerInvocationsDeleted).toBe(0);
expect(result.scheduledTriggerInvocationsDeleted).toBe(0);
expect(result.datasetRunsDeleted).toBe(0);
expect(result.evaluationRunsDeleted).toBe(0);

Refs:

🕐 Pending Recommendations (1)

  • 🟡 scheduledTriggerSteps.ts:293 Silent ref resolution (pullfrog) — invocations created without refs won't be cleaned up by branch cascade delete

💡 APPROVE WITH SUGGESTIONS

Summary: The test coverage gap identified in the prior review has been thoroughly addressed. The new tests comprehensively cover cascade-delete for all 4 new runtime tables across branch, project, and agent scopes. The implementation is well-structured and follows existing patterns.

The remaining minor issue about null ref handling is a pre-existing pattern inconsistency that surfaced during this PR — it's worth addressing but not a blocker. Nice work! 🎉

Discarded (2)
Location Issue Reason Discarded
cascadeDelete.test.ts No test for entities with ref: null Edge case outside PR scope — existing records predate this feature, and null refs are correctly excluded from branch-based deletion
0023_bumpy_vampiro.sql:4 Missing trailing newline Consistent with drizzle-kit generation pattern in this codebase
Reviewers (5)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-tests 2 0 1 0 0 0 1
pr-review-standards 0 0 0 0 0 0 0
pr-review-breaking-changes 1 0 0 0 0 0 1
pr-review-consistency 0 0 0 0 0 0 0
pr-review-precision 0 0 0 0 0 0 0
Orchestrator - 0 0 0 1 1 0
Total 3 0 1 0 1 1 2

Note: The inline comment addresses an issue identified by pullfrog in a prior review that remains unresolved in this delta.

projectId: params.projectId,
agentId: params.agentId,
scheduledTriggerId: params.scheduledTriggerId,
ref: resolvedRef ?? undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Minor: Inconsistent null handling for ref resolution

Issue: When resolvedRef is null, this code silently continues with undefined, creating a record without branch tracking. However, checkTriggerEnabledStep (line 174) treats the same scenario as a hard failure: 'Failed to resolve ref for project, treating trigger as deleted'.

Why: Invocations created without refs cannot be cleaned up by the branch-scoped cascade delete logic that this PR enables. This creates potential orphan records.

Fix: Consider either:

  1. Failing early when resolvedRef is null (consistent with checkTriggerEnabledStep)
  2. Adding a warning log when creating invocations without refs
if (!resolvedRef) {
  logger.warn(
    { tenantId: params.tenantId, projectId: params.projectId },
    'Creating invocation without ref - will not be cleaned up by branch cascade delete'
  );
}

Refs:

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(1) Total Issues | Risk: Low

🟠⚠️ Major (1) 🟠⚠️

Inline Comments:

  • 🟠 Major: scheduledTriggerSteps.ts:177 Truncated log message introduced by copy-paste error

🕐 Pending Recommendations (2)

  • 💭 cascadeDelete.test.ts (prior review) — Consider adding explicit zero-count assertions in cascadeDeleteBySubAgent tests
  • 🟡 scheduledTriggerSteps.ts:293 (pullfrog) — Silent ref resolution creates invocations that won't be cleaned up by branch cascade delete

🚫 REQUEST CHANGES

Summary: The delta addresses the prior feedback about silent ref resolution by adding a warning log — good addition! However, a copy-paste error introduced a truncated log message at line 177 that needs a quick fix. The inline comment has a 1-click suggestion to restore the original complete message.

Discarded (0)

No discarded items.

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
Orchestrator 1 0 0 0 1 2 0
Total 1 0 0 0 1 2 0

Note: Delta review — sub-agents not dispatched due to small scope (1 file, ~8 lines).

@github-actions github-actions bot deleted a comment from claude bot Mar 13, 2026
@vercel vercel bot temporarily deployed to Preview – agents-docs March 13, 2026 21:05 Inactive
@miles-kt-inkeep
Copy link
Contributor Author

@claude add a changeset

@inkeep-internal-ci
Copy link
Contributor

inkeep-internal-ci bot commented Mar 13, 2026

Claude finished @miles-kt-inkeep's task in 37s —— View job


Adding changeset

  • Create changeset for agents-core and agents-api
  • Commit and push

Added changeset fast-spies-design.md with a patch bump for both @inkeep/agents-core and @inkeep/agents-api.
|

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

✅ Delta Changes Verified

The delta since the last review contains:

  1. Truncated log message fix — Line 177 now correctly reads 'Failed to resolve ref for project, treating trigger as deleted' instead of the truncated 'Failed to resolve ref for project, run will not be'. ✅
  2. Changeset added — Proper versioning with patch bumps for @inkeep/agents-api and @inkeep/agents-core. ✅

🕐 Pending Recommendations (1)

  • 💭 cascadeDelete.test.ts (prior review) — Consider adding explicit zero-count assertions in cascadeDeleteBySubAgent tests for triggerInvocationsDeleted, scheduledTriggerInvocationsDeleted, datasetRunsDeleted, and evaluationRunsDeleted

✅ APPROVE

Summary: The blocking issue from the previous review (truncated log message) has been fixed. The PR correctly adds ref columns to 4 runtime tables for branch tracking support, with comprehensive cascade-delete logic and thorough test coverage. Ship it! 🚀

Discarded (0)

No discarded items.

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
Orchestrator 0 0 0 0 0 1 0
Total 0 0 0 0 0 1 0

Note: Delta review — sub-agents not dispatched due to minimal scope (1 line change + changeset).

@github-actions github-actions bot deleted a comment from claude bot Mar 13, 2026
miles-kt-inkeep and others added 6 commits March 17, 2026 12:54
…s.ts

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: miles-kt-inkeep <miles-kt-inkeep@users.noreply.github.com>
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

✅ Delta Changes Verified

The delta since the last approved review (929a5da72) contains 2 commits:

  1. babe70f2b test ci trigger — CI test commit, no functional changes.

  2. 1cd775e7e narrow ref schema — Addresses @amikofalvy's concern about broad types:

    • Before: Drizzle's createSelectSchema inferred overly broad anyOf types for JSONB ref columns (string | number | boolean | object | array | null)
    • After: Explicitly typed as ResolvedRefSchema.nullable().optional() across 7 select schemas
    • OpenAPI: Correctly narrowed from broad anyOf to $ref: "#/components/schemas/ResolvedRef" with allOf + nullable: true

Schemas updated:

  • TriggerInvocationSelectSchema
  • ScheduledTriggerInvocationSelectSchema
  • TaskSelectSchema
  • ConversationSelectSchema
  • ContextCacheSelectSchema
  • DatasetRunSelectSchema
  • EvaluationRunSelectSchema

🕐 Pending Recommendations (1)

  • 💭 cascadeDelete.test.ts (prior review) — Consider adding explicit zero-count assertions in cascadeDeleteBySubAgent tests for triggerInvocationsDeleted, scheduledTriggerInvocationsDeleted, datasetRunsDeleted, and evaluationRunsDeleted

✅ APPROVE

Summary: The type narrowing fix correctly addresses the human reviewer's concern about overly broad types in the OpenAPI schema. The ref fields now properly reference the structured ResolvedRef schema ({type, name, hash}) instead of accepting arbitrary JSON values. This is a good improvement that strengthens the API contract. Ship it! 🚀

Discarded (0)

No discarded items.

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
Orchestrator 0 0 0 0 0 1 0
Total 0 0 0 0 0 1 0

Note: Delta review — sub-agents not dispatched due to minimal scope (type narrowing only, 2 files changed).

@github-actions github-actions bot deleted a comment from claude bot Mar 17, 2026
@pullfrog
Copy link
Contributor

pullfrog bot commented Mar 17, 2026

TL;DR — Adds a nullable ref (jsonb ResolvedRef) column to four runtime tables — triggerInvocations, scheduledTriggerInvocations, datasetRun, and evaluationRun — so every invocation and run records which branch it was created on. This is groundwork for branch-scoped trigger execution.

Key changes

  • runtime-schema.ts — Adds ref: jsonb('ref').$type<ResolvedRef>() to triggerInvocations, scheduledTriggerInvocations, datasetRun, and evaluationRun tables.
  • schemas.ts — Extends TriggerInvocationSelectSchema, ScheduledTriggerInvocationSelectSchema, TaskSelectSchema, ConversationSelectSchema, ContextCacheSelectSchema, DatasetRunSelectSchema, and EvaluationRunSelectSchema with ref: ResolvedRefSchema.nullable().optional().
  • cascade-delete.ts — Adds branch-scoped deletion of the four new ref-bearing tables to cascadeDeleteByBranch, cascadeDeleteByProject, and cascadeDeleteByAgent.
  • scheduledTriggers.ts — Replaces inline resolveRef calls in the manual-run and rerun handlers with the pre-resolved c.get('resolvedRef') from middleware.
  • scheduledTriggerSteps.ts — Resolves and attaches ref when creating invocations in the workflow step, with a warning log on resolution failure instead of throwing.
  • Eval/trigger services — Threads ref/resolvedRef through EvaluationService, conversationEvaluation, evaluationJob, TriggerService, datasetRunConfigs, and evaluationJobConfigs route handlers.
  • cascadeDelete.test.ts — Extends branch, project, and agent cascade-delete tests to seed and assert deletion of all four new entity types.
  • Migration 0023_bumpy_vampiro.sql — Four ALTER TABLE … ADD COLUMN "ref" jsonb statements.

Summary | 18 files | 7 commits | base: mainfeat/add-runtime-refs


ref column on runtime tables

Before: triggerInvocations, scheduledTriggerInvocations, datasetRun, and evaluationRun had no branch information — all rows were implicitly main.
After: Each table has a nullable ref jsonb column typed as ResolvedRef, populated at creation time from the request's resolved ref.

The column is nullable so existing rows remain valid and work-app tables (edited only on main) are intentionally excluded. Route handlers and services now thread the resolved ref from middleware (c.get('resolvedRef')) or resolve it inline (workflow steps) and pass it into insert calls.

runtime-schema.ts · schemas.ts · 0023_bumpy_vampiro.sql


Ref resolution moved out of scheduled trigger route handlers

Before: The manual-run and rerun handlers in scheduledTriggers.ts called resolveRef(manageDbClient) inline, duplicating logic and importing getProjectScopedRef + resolveRef + manageDbClient.
After: Both handlers use the already-resolved c.get('resolvedRef') from middleware. The three now-unused imports are removed.

The workflow step (createInvocationIdempotentStep) still resolves its own ref since it runs outside the HTTP middleware context, and logs a warning instead of throwing on failure.

scheduledTriggers.ts · scheduledTriggerSteps.ts


Cascade delete covers new tables

Before: Branch/project/agent cascade deletes only cleaned up conversations, tasks, and contextCache.
After: cascadeDeleteByBranch, cascadeDeleteByProject, and cascadeDeleteByAgent also delete matching triggerInvocations, scheduledTriggerInvocations, datasetRun, and evaluationRun rows. cascadeDeleteByAgent skips datasetRun/evaluationRun (not agent-scoped). cascadeDeleteBySubAgent returns zeroes for all four.

Why are datasetRun and evaluationRun skipped in agent-scoped deletes? These tables are project-scoped (no agentId column), so they're only cleaned up in branch-scoped and project-scoped cascades. The agent cascade correctly reports 0 for both.

cascade-delete.ts · cascadeDelete.test.ts

Pullfrog  | View workflow run | Using Claude Code | Triggered by Pullfrogpullfrog.com𝕏

@itoqa
Copy link

itoqa bot commented Mar 17, 2026

Ito Test Report ❌

16 test cases ran. 14 passed, 2 failed.

✅ Most routing, authorization, replay-protection, and XSS-safety checks behaved as expected. 🔍 Code-first verification confirms two real product defects: duplicate manual dispatch is possible on rapid Run Now actions, and branch deletion can fail with an internal error because branch removal uses non-force Dolt deletion.

✅ Passed (14)
Test Case Summary Timestamp Screenshot
ROUTE-1 Run Now dispatched successfully and produced one new invocation row with expected lifecycle start. 15:54 ROUTE-1_15-54.png
ROUTE-2 Rerun created a new invocation entry and preserved prior invocation history unchanged. 15:54 ROUTE-2_15-54.png
ROUTE-3 Triggered rerun from conversation trace panel and confirmed a distinct newer invocation row appeared in All Scheduled Trigger Invocations. 54:45 ROUTE-3_54-45.png
LOGIC-2 Branch-scoped dataset/evaluator/config creation succeeded and resulting dataset_run plus linked evaluation_run records carried ref.name default_activities-planner_qa-branch-x. 40:31 LOGIC-2_40-31.png
LOGIC-4 Disposable project qa-delete-project was created, seeded with runtime entities, then deleted successfully (204) and became unreachable (404) with project list showing only baseline project afterward. 43:22 LOGIC-4_43-22.png
EDGE-1 Zero-match job filter returned the expected empty run metadata with queued=0, failed=0, conversationCount=0, and empty evaluationRunId. 21:00 EDGE-1_21-00.png
EDGE-2 Run endpoint returned 200 and immediate rerun on running invocation returned 400 bad_request with state-guard message. 45:00 EDGE-2_45-00.png
EDGE-3 Both fabricated-invocation rerun attempts returned 404 not_found with 'Invocation not found'. 45:00 EDGE-3_45-00.png
EDGE-5 After reload and navigation churn, filters and row actions remained consistent with no stale handler behavior observed. 15:54 EDGE-5_15-54.png
EDGE-6 At 390x844, status filters and invocation action menu remained reachable and operable for rerun workflows. 15:54 EDGE-6_15-54.png
ADV-1 Unauthenticated run and rerun requests were both rejected with 401 unauthorized. 45:00 ADV-1_45-00.png
ADV-2 Tampered delete request targeted qa-branch-x and did not impact qa-branch-y; foreign branch remained present and queryable, indicating safe handling of tampered ref context. 25:10 ADV-2_25-10.png
ADV-3 Injected was stored and rendered as inert text in list/detail/edit surfaces, with no script execution or alert dialog observed. 48:24 ADV-3_48-24.png
ADV-4 Cross-scope replay attempt returned 404 not_found and did not execute rerun in mismatched scope. 45:00 ADV-4_45-00.png
❌ Failed (2)
Test Case Summary Timestamp Screenshot
LOGIC-3 Branch delete for qa-branch-x returned internal_server_error from DOLT_BRANCH drop while qa-branch-y remained queryable, so expected branch cascade deletion behavior did not complete. 43:22 LOGIC-3_43-22.png
EDGE-4 Duplicate dispatch occurred: two new running invocations were created at the same timestamp from one rapid double-click intent. 15:54 EDGE-4_15-54.png
Branch deletion cascades newly added runtime tables – Failed
  • Where: Manage branch deletion endpoint for project branch cleanup.

  • Steps to reproduce: Create/use a non-main project branch with activity, then call delete branch for that branch.

  • What failed: The API returned 500 internal_server_error from DOLT_BRANCH('-d', ...) instead of deleting the branch successfully.

  • Code analysis: I reviewed the manage branch delete route and Dolt branch deletion implementation. The route expects successful deletion (204), but the low-level delete call always uses non-force -d, which fails on non-fully-merged branches and bubbles up as a server error.

  • Relevant code:

    agents-api/src/domains/manage/routes/branches.ts (lines 241-250)

    // First delete runtime entities associated with this branch
    const fullBranchName = `${tenantId}_${projectId}_${branchName}`;
    await cascadeDeleteByBranch(runDbClient)({
      scopes: { tenantId, projectId },
      fullBranchName,
    });
    
    // Then delete the branch from the config DB
    await deleteBranch(db)({ tenantId, projectId, name: branchName });
    return c.body(null, 204);

    packages/agents-core/src/dolt/branch.ts (lines 31-36)

    export const doltDeleteBranch =
      (db: AgentsManageDatabaseClient) =>
      async (params: { name: string; force?: boolean }): Promise<void> => {
        const flag = params.force ? '-D' : '-d';
        await db.execute(sql.raw(`SELECT DOLT_BRANCH('${flag}', '${params.name}')`));
      };

    packages/agents-core/src/dolt/branches-api.ts (lines 254-256)

    // Delete the branch
    await doltDeleteBranch(db)({ name: fullName });
  • Why this is likely a bug: The endpoint contract expects branch deletion, but production code always uses non-force branch deletion, which predictably fails for valid branch states and causes a 500 instead of a handled delete path.

  • Introduced by this PR: No - pre-existing bug (code not changed in this PR).

  • Timestamp: 43:22

Rapid double-click Run now should not create duplicate invocations – Failed
  • Where: Manual "Run Scheduled Trigger Now" endpoint and invocation creation path.

  • Steps to reproduce: Open a scheduled trigger and rapidly double-click Run Now (or submit two near-simultaneous requests).

  • What failed: Two invocation rows were created for one user intent instead of a single deduplicated invocation.

  • Code analysis: I inspected the manual run and rerun creation logic and idempotency model. The schema has a unique idempotency key, but manual runs generate a fresh timestamp-based key per request, so concurrent clicks are always treated as distinct and both insertions succeed.

  • Relevant code:

    agents-api/src/domains/manage/routes/scheduledTriggers.ts (lines 1349-1359)

    await createScheduledTriggerInvocation(runDbClient)({
      id: invocationId,
      tenantId,
      projectId,
      agentId,
      scheduledTriggerId,
      ref: resolvedRef,
      status: 'pending',
      scheduledFor: new Date().toISOString(),
      idempotencyKey: `manual-run-${scheduledTriggerId}-${Date.now()}`,
      attemptNumber: 1,
    });

    packages/agents-core/src/db/runtime/runtime-schema.ts (lines 367-375)

    idempotencyKey: varchar('idempotency_key', { length: 256 }).notNull(),
    createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' })
      .notNull()
      .defaultNow(),

},
(table) => [
primaryKey({ columns: [table.tenantId, table.id] }),
uniqueIndex('sched_invocations_idempotency_idx').on(table.idempotencyKey),
]


**`agents-api/src/domains/run/workflow/steps/scheduledTriggerSteps.ts`** (lines 269-279)

```ts
const existing = await getScheduledTriggerInvocationByIdempotencyKey(runDbClient)({
  idempotencyKey: params.idempotencyKey,
});

if (existing) {
  logger.info(
    { scheduledTriggerId: params.scheduledTriggerId, idempotencyKey: params.idempotencyKey },
    'Invocation already exists, skipping creation'
  );
  return { invocation: existing, alreadyExists: true };
}
  • Why this is likely a bug: The production path assigns unique idempotency keys per click, so duplicate user actions are not coalesced and duplicate dispatch is structurally allowed.
  • Introduced by this PR: No - pre-existing bug (relevant duplicate-prevention logic was not introduced by this PR changes).
  • Timestamp: 15:54
📋 View Recording

Screen Recording

@miles-kt-inkeep miles-kt-inkeep added this pull request to the merge queue Mar 18, 2026
Merged via the queue into main with commit 15c6752 Mar 18, 2026
11 checks passed
@miles-kt-inkeep miles-kt-inkeep deleted the feat/add-runtime-refs branch March 18, 2026 15:45
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.

2 participants