You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Reopening a done task fails to clear completed_at when the caller also sends completed_at: null #31
Found while implementing #3. This is an upstream platform limitation (@objectstack/objectql 17.2.0), recorded here because the symptom is a wrong duly_task record.
Symptom
Reopening a completed task normally clears completed_at — the #3 hook writes null on the transition out of done, and test/task-hook.test.ts pins it. But if the caller's payload also carries completed_at: null, the clear is silently dropped and the record keeps a completion timestamp it no longer earns:
Result: an in_progress task carrying a completion timestamp. No error is raised. The validation rule completed_at_required_when_done does not catch it — that rule only fires on status == 'done'.
Cause
stripReadonlyFields decides whether a readonly key in the payload is a caller write by comparing it to the pre-hook snapshot:
The Object.is guard is what normally lets a hook-derived value survive: the hook overwrote the caller's value, so they differ, so the key is kept. It cannot distinguish "the hook deliberately wrote the same value" from "the hook never touched it". When the hook's intended value and the caller's value are both null, the hook's write is deleted along with the caller's.
This contradicts the contract the platform states in its own strip diagnostics:
A value DERIVED by a beforeUpdate hook is not a caller write and is never stripped
That holds only when the derived value differs from the supplied one.
Scope
Narrow but real. Unaffected: a bare { status: 'in_progress' } reopen, and any form that round-trips the record (it sends the actual timestamp, which differs from null, so the hook's clear survives). Affected: an API client that explicitly nulls the field while reopening.
A consumer-side workaround (writing a sentinel that is not Object.is-equal to null) would be a hack around a producer defect and would make the hook's behaviour depend on an engine internal. The fix belongs upstream — the strip should track keys a hook actually wrote rather than infer it by value comparison.
Needs a maintainer decision on whether to carry a local workaround until the platform fix lands.
The judgement to leave it alone rather than patch around it in the hook was right, and it is worth stating why: a consumer-side workaround for a producer defect is how a workaround becomes permanent. It also would have hidden the only evidence that the strip is wrong.
Closing here; the fix and its tracking live upstream.
Activity
os-warren commentedon Sep 1, 2026
Routed to the framework repo and closed here — this is an upstream defect in
packages/objectql, not a duly card.Filed as objectstack-ai/objectstack#14088, with the measurement and the reason the workaround was correctly refused.
The judgement to leave it alone rather than patch around it in the hook was right, and it is worth stating why: a consumer-side workaround for a producer defect is how a workaround becomes permanent. It also would have hidden the only evidence that the strip is wrong.
Closing here; the fix and its tracking live upstream.
Generated by Claude Code