Auto-resolve
Auto-resolve is how most tasks complete themselves. A task with an auto-complete checklist item carries a scheduled fire time and a list of conditions; at fire time, the cron evaluates the conditions and — if all are met — runs the task's action and marks it done. If any condition is not met, the operator can optionally be notified.

The data model
An auto-resolve task has at least one checklist item like this:
{
"label": "[Auto-complete] Complete at 19:00 (when 1 condition met)",
"automaticType": "scheduled_time",
"automaticConfig": {
"time": "19:00",
"conditions": [
{
"property": "messageThread.hasUnansweredGuestMessage",
"operator": "equals",
"value": "false",
"notifyOnNotMet": true
}
]
}
}
time— the wall-clock time the cron evaluates this item (in the tenant's local timezone).conditions— boolean checks against the reservation + guest + message state.notifyOnNotMet— if true, when fire time arrives and the condition fails, the operator gets a WhatsApp ping.
How the cron evaluates
Two cron jobs in cronJobs.ts:
- morningCron (06:00 CET)
- afternoonCron (15:00 CET)
…both call dailyTaskSeeder, which:
- Iterates every reservation in the ±1 day window from today.
- For each task template that fires on
check-in/check-out/after_checkout, re-evaluates whether a task should exist for that reservation. - If yes and no live task exists yet → seed one.
- For existing auto-resolve tasks whose scheduled time falls in the window → evaluate conditions, fire or skip.
The ±1 day window is documented in server/services/reservationEventFiring.ts.
Common conditions
reservation.nukiAccessCode is_not_null— Send Check-in precondition (PIN exists).messageThread.hasUnansweredGuestMessage equals false— pre-checkout reminder precondition (guest's last message was answered).task.dependencies.allDone equals true— fire only after dependent tasks complete.reservation.guestMood notIn ['angry', 'disputed']— thank-you message precondition.reservation.checkoutTime is_not_null— for time-relative tasks.
The full property catalogue lives in platformCapabilities.ts and is autodiscovered into the task template editor condition picker.
Why a task gets blocked
When fire time arrives and a condition is false, the cron:
- Logs the failure to
app_errors. - If
notifyOnNotMet: true, sends the operator a WhatsApp message naming the task, reservation, and failing condition. - Leaves the task in
scheduledstatus (next morning cron will re-evaluate).
Example operator-facing message:
Task 216874 (Castle&River Luxe-Vydrica) Auto-resolve was BLOCKED because the condition is not met: reservation.nukiAccessCode is_not_null. Please resolve this task manually.
What to do — see Send Check-in for the classic case.
Skipped actions are never silent
When an action internally bails out (e.g. WhatsApp sendReply skipped because the guest opted out):
- Auto-resolve runs the action.
- If the action returns
skipped: truewith a reason, the task moves tofailedwith that reason. - The reason appears in the Activity Log and on the task card.
The same applies to assign_cleaner — a skip because the cleaner was unassigned is visible, never silent.
What an operator should know
- Every auto-resolve task has a schedule (when it'll fire) and conditions (what must be true for it to fire).
- You can see both on the task card.
- If a condition is currently failing, you have until the fire time to fix it — usually by replying to the guest or generating the missing PIN.
- If you miss the window, the operator-notification will arrive at fire time and you'll have to manually trigger.