Skip to content

No production signal when a scheduled sweep runs and does nothing #568

Description

@yinlianghui

Recording a real gap. Deliberately not implementing yet — the reasoning for deferring is below, and it is the substance of this issue.

Upstream: objectstack-ai/objectstack#4354 tracks the platform-side fix. This issue stays open as the hotcrm-side record of the gap and of the decision not to work around it locally.

The gap

A scheduled sweep that selects records and then acts on none is indistinguishable, from outside, from a sweep that had no work to do. Both:

  • report success: true
  • log nothing
  • run on time, every time
  • write zero records

This is what let #567 / objectstack#4347 live. Three flows (opportunity_stagnation, contract_renewal, campaign_enrollment) ran daily and produced nothing, for as long as they had existed. Nobody noticed, because there was nothing to notice.

It was caught by the runtime tests added in #563/#566 — not by anything in production. Had those flows not been in the tested set, the bug would still be live.

Why this is not urgent

  • The specific defect is fixed (fix(flows): unbreak main — contract_renewal throws; rewrite the two tests #565 turned red #566), with a guard in test/flow-scheduled.test.ts that walks every registered flow and fails if any loop body reintroduces a bare string condition.
  • Runtime coverage now stands at 24/24 hooks and 17/20 flows, and test/runtime-coverage.test.ts fails when a new hook or flow arrives untested. That is the actual defense, and it exists.
  • Blast radius is already bounded: every sweep's query carries limit: 500 (campaign_enrollment 1000), so even a fully-drained backlog is capped per run.

Why an app-level detector was considered and rejected

The obvious app-side fix is a counter: assignment nodes tallying selected-vs-acted inside the loop, a decision comparing them, and a notify when a sweep selects > 0 and acts on 0.

It was rejected, for now, on these grounds:

  1. The detector would be built from the primitives that just proved able to fail silently. A silent-no-op detector implemented with decision gates inside a loop body has exactly the failure mode it is meant to catch. When it breaks, it breaks quietly, and we are back where we started with more code.
  2. script nodes cannot help. The built-in runtime treats inline config.script as a no-op and logs no server-side JS sandbox — this node is a no-op. Emitting a run summary from a flow would require registering real functions, which is a larger commitment than the problem currently justifies.
  3. The engine already has the data. AutomationEngine carries recordLog / executionLog / listRuns internally. The information exists; it is simply not surfaced. Building a parallel app-level mechanism now means maintaining it forever and discarding it when the platform exposes the real thing.
  4. Whether the counter approach even works is unverified. Whether an assignment inside a loop body accumulates across iterations has not been tested. Proposing it as the plan of record without that check would repeat the mistake this whole thread is about.

The actual fix

Platform-side, tracked in objectstack-ai/objectstack#4354: surface flow run summaries — records selected, acted on, skipped — in the console and make them queryable. Then "selected 30, acted 0" is visible on day one, for every flow, with no per-flow instrumentation and nothing for an app to maintain.

Revisit when

  • objectstack#4354 ships → adopt it, close this; or
  • a second silent-no-op incident occurs that the test suite did not catch → the cost/benefit changes and the app-level detector becomes worth its risks

Not to be confused with

The one-time backlog burst on first run after #566 deploys — every currently-stalled deal gets nudged, every contract already inside its notice window gets a renewal task. That is a migration consequence of fixing the bug, not a defect, and it is bounded by the existing limit: 500.

Related: #495, #563, #566, #567, objectstack-ai/objectstack#4347, objectstack-ai/objectstack#4354.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions