Repository navigation
Description
Found while implementing #6882 (arming the task_completion record-change flow). Filed standalone: it is inside a node body, not the trigger wiring, and repairing it needs a decision about which authoring surface should compute the next due date.
The defect
examples/app-todo/src/flows/task.flow.ts, TaskCompletionFlow node create_next_task:
config: {
objectName: 'todo_task',
fields: {
...
due_date: 'DATEADD({completedTask.due_date}, {completedTask.recurrence_interval}, "{completedTask.recurrence_type}")',
...
},
},Two independent facts:
DATEADDdoes not exist.git grep -n DATEADD origin/main -- packagesreturns nothing, andpackages/formularegisters no such function under any casing. It is not a CEL builtin here and it is not an ObjectStack formula function.- A
create_recordnode'sfieldsvalues are TEMPLATE-interpolated, not formula-evaluated. The{completedTask.x}holes are filled and the surrounding text is passed through verbatim, so what reaches the engine is the literal stringDATEADD(2026-08-10, 1, "daily").
The write is then refused by the field's own coercion.
Measured
Real kernel (ObjectQL + automation + record-change trigger + sqlite) with the #6882 repair applied, one recurring task transitioned into completed:
[record-change] bound flow 'task_completion' -> afterUpdate on 'todo_task'
[automation] run flow=task_completion run=run_4e766d69... status=failed durationMs=70 selected=1 acted=0 skipped=0
ERROR Trigger-fired run of flow 'task_completion' failed (trigger 'record_change')
{"error":"Node 'create_next_task' failed: create_record(todo_task) failed: Due Date must be a valid date (ISO-8601)"}
The non-recurring path is unaffected and runs green end to end (start:success -> get_task:success -> check_recurring:success -> create_next_task:skipped, run status completed), because edge e4 routes past this node when is_recurring != true.
Reachability changed with #6882, the defect did not
Before #6882 the flow never bound, so this node never executed and the dead function text was inert. After #6882 the flow fires on every completion, so a recurring task's completion now produces a failed run with the message above instead of silently doing nothing. That is strictly more informative than the previous state (a run that never happened, reported nowhere), but the recurrence feature the node exists for still does not work.
Shape of the fix (the decision)
The flow needs "previous due date plus N units", and it is not obvious which surface should own it:
- A
scriptnode (config.function, registered indefineStack({ functions })) computing the next date and handing it tocreate_next_taskthrough a variable — this is the shape the showcase uses for computed values in a flow (summarizeCompletedTask), and a flow function is PURE (takesinputs, returns a value) with a later declarative node persisting it (Ascriptnode's function is contractually pure, but nothing enforces it — a function that writes makes its run under-report #4396). - An
assignmentnode — only if the expression dialect available there can do date arithmetic; needs checking against whatpackages/formulaactually registers. - A
formulafield ontodo_task(next_due_date) that the flow simply reads — moves the arithmetic to the schema, where it is also visible to list views. - Drop the recurrence spawn — if per-completion recurrence is not a semantic the example wants to demonstrate, the whole
create_next_tasknode plus edgee3goes and the flow ends atcheck_recurring.
Whichever it is, DATEADD should not survive in any of them: an invented function name in a shipped example app is exactly the shape an AI author copies.
Activity
os-project-manager commentedon Aug 9, 2026
Triage (maintainer-authorized one-off pass #2, 2026-08-09, registered on #6015): routed
domain:services, gradedpm:queue. Reference-corpus defect: the flow writes a literalDATEADD(...)string intodue_dateand the driver refuses the create — the recurrence branch has never worked. The "which surface computes the next date" question is implementation-level: use whatever date-arithmetic the platform actually ships (script node / supported formula function); if measurement shows NO shipped capability can express it, STOP and report — that would be a genuine gap card, not this one.Generated by Claude Code
os-help commentedon Aug 10, 2026
Claim: PM loop round 1 (
domain:servicesseat #6021)Session:
session_015fkdTyGmMD5s8ZtEifvuGyBranch:
claude/issue-7037-recurrence-due-dateWorktree:
objectstack-issue-7037Domain:
domain:servicesFile surface:
examples/app-todo/src/flows/task.flow.ts, the app's function-registration site (defineStack({ functions }), expected in the app index) if the script-node route is taken, and a NEW test file (suggestedexamples/app-todo/test/task-recurrence.test.ts). ⛔ NOTtest/task-completion-trigger.test.tsand NOTsrc/objects/task.object.ts/src/hooks/task.hook.ts/src/actions/task.handlers.ts(sibling #7036's region). (Stop on breach; explain in the report.)Container & model: M,
mode:subagent,model: opusSerial constraints cleared: same-package sibling #7036 dispatched in this same round under the maintainer's explicit dispatch-all instruction — regions disjoint by declaration (see both claim comments). Pair discipline: merge
origin/mainbefore opening the PR, merge again after the sibling lands, merge queue arbitrates.Generated by Claude Code
os-help commentedon Aug 10, 2026
ACCEPT. PR #7227, reviewed by the
domain:servicesseat (#6021, sessionsession_015fkdTyGmMD5s8ZtEifvuGy). Marking ready and queueing.Verified against GitHub rather than the report: 6 files —
src/flows/task.flow.ts,objectstack.config.ts(function registration), newsrc/functions/(barrel +computeNextTaskDueDate), newtest/task-recurrence.test.ts, one patch changeset. Region discipline held: none of sibling #7036's four files touched (verified on the diff, not the report). 25/25 check runs completed, zero failures; ESLintsuccess(03:16:43Z), TypeScript Type Checksuccess(03:19:05Z), read by job conclusion.The triage ruling was followed with measurement, not assumption: the dev enumerated
FLOW_NODE_EXPRESSION_PATHSand theassignmentnode'sinterpolate()to establish there is NO value-producing expression slot in the flow vocabulary — so thescriptnode (theshowcase_task_completedshape, #1870/#4396) is the shipped surface, and the ruling's STOP-and-report branch correctly did not trigger. The recurrence arithmetic clamps monthly shifts exactly as@objectstack/formula'saddMonths, so the example cannot teach a semantic that disagrees with the platform's own formula function; unknown cadences and forbidden intervals refuse loudly.Both halves of the card closed: the recurring path now completes end-to-end on a real kernel (spawned task's
due_dateasserted for daily/weekly/monthly), andDATEADDsurvives in no authored value — pinned by name againstallFlowsAND by a class-level guard (no write node in any app flow may carry function-call text in a field value: the check that would have caught this at authoring time). The reverse fixture deliberately pins the CLASS with a fresh invented name, keeping the dead name out of the corpus.Changeset correctly included — the dev measured that
examples/**IS changeset-versioned here (package versioned, noignorein config, #6882's sibling PR precedent), overriding the dispatch prompt's uncertainty. Check Changeset green confirms.Generated by Claude Code
fix(example-todo): `task_completion` computes a real next recurrence …