Trigger sensor only fires once, but based on one that fires perfectly

I’m at my wits end with a trigger based sensor which is based on the same pattern as one that has been working for 6 months. They both have this structure:

  - trigger:
      - trigger: time_pattern
        minutes: "8"
      - trigger: time_pattern
        minutes: "23"
      - trigger: time_pattern
        minutes: "38"
      - trigger: time_pattern
        minutes: "53"
    action:
      - service: rest_command.getgnss15
        response_variable: gnss
      - variables:
          kalman: >
            {%- set kalman = [pred_lo, [pred_hi, kalman_raw] | min] | max %}
            {{ kalman }}
# there are a load of these a handful of which use attributes from the previous firing: like
          this_t: >
            {%- set prev = states.sensor.gnss15k2da.attributes if states.sensor.gnss15k2da is defined and states.sensor.gnss15k2da.attributes is defined else {} %}
            {%- set prevt = prev.get('this_t') %}
            {%- set this_t = feeds[1].created_at if feeds | length > 1 else prevt %}
            {{ this_t }}

    sensor:
      - name: gnss15k2da
        unique_id: gnss15k2da
        state: >
          {{ kalman | round(4) }}
        state_class: measurement
        unit_of_measurement: m
        attributes:
          fired_at: "{{ utcnow() | string }}"

The original sensor is gnss15 and fires every time. This derivative is more complex (a 2D kalman filter vs the original 1D filter), but the structure is the same. It fires exactly once after it is created (or renamed) then never again. In case it was a doom loop caused by using its previous attributes I created a separate trigger sensor to store its attributes 1 minute afterwards with its own attribute fired_at and that one also only ever fired the once. I can duplicate the original with a new name and that fires every time too. In case the times clash I’ve notused exactly the same time triggers.
There is nothing in the logs and the exact code shows no errors in developer tools.
I have spent several days on this, with and without the help of Claude AI and I’m completely stuck. Can anyone shed any light on what is going wrong?
I’m running HAos on an x286 miniPC with everything up-to-date.

Post the entire configuration that this entity is in. Also post any errors in your logs. If you are on the current release, errors in your logs will say your exact entity_id.

It is very long…

  - trigger:
      - trigger: time_pattern
        minutes: "8"
      - trigger: time_pattern
        minutes: "23"
      - trigger: time_pattern
        minutes: "38"
      - trigger: time_pattern
        minutes: "53"
    action:
      - service: rest_command.getgnss15
        response_variable: gnss

      - variables:

          feeds: >
            {%- set feeds = gnss.content.feeds if gnss is defined and gnss.content is defined and gnss.content.feeds is defined else [] %}
            {{ feeds }}

          this_t: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set prevt = prev.get('this_t') %}
            {%- set this_t = feeds[1].created_at if feeds | length > 1 else prevt %}
            {{ this_t }}

          timeslot: >
            {%- set ts = (utcnow() - timedelta(minutes=38)).timestamp() %}
            {%- set timeslot = (900 * (ts // 900)) | timestamp_utc %}
            {{ timeslot }}

          last_t: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set last_t = prev.get('this_t') %}
            {{ last_t }}

          feed_stale: >
            {%- set this_dt = this_t | as_datetime(none) %}
            {%- set last_dt = last_t | as_datetime(none) %}
            {%- set feed_stale = this_dt is not none and last_dt is not none and this_dt <= last_dt %}
            {{ feed_stale }}

          feed_dead: >
            {%- set this_dt = this_t | as_datetime(none) %}
            {%- set slot_dt = timeslot | as_datetime(none) %}
            {%- set age_mins = ((slot_dt - this_dt).total_seconds() / 60) if this_dt is not none and slot_dt is not none else 0 %}
            {%- set feed_dead = age_mins > 120 %}
            {{ feed_dead }}

          nogap: >
            {%- set this_dt = this_t | as_datetime(none) %}
            {%- set last_dt = last_t | as_datetime(none) %}
            {%- set ok_fetch = gnss.status == 200 %}
            {%- set ok_times = this_dt is not none and last_dt is not none %}
            {%- set gap_mins = ((this_dt - last_dt).total_seconds() / 60) if ok_times else 0 %}
            {%- set nogap = ok_fetch and ok_times and not feed_stale and gap_mins >= 10 and gap_mins <= 20 %}
            {{ nogap }}

          pred_lo: >
            {%- set pred_lo = -1.0 %}
            {{ pred_lo }}

          pred_hi: >
            {%- set pred_hi = 10.0 %}
            {{ pred_hi }}

          thisobs: >
            {%- set thisobs = feeds[1].field1 | float(0) if feeds | length > 1 else 0 %}
            {{ thisobs }}

          rest_thispred: >
            {%- set rest_thispred = feeds[1].field2 | float(none) if feeds | length > 1 else none %}
            {{ rest_thispred }}

          rest_lastpred: >
            {%- set rest_lastpred = feeds[0].field2 | float(none) if feeds | length > 0 else none %}
            {{ rest_lastpred }}

          stored_thispred: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set stored_thispred = prev.get('thispred', none) | float(none) %}
            {{ stored_thispred }}

          stored_next_pred: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set stored_next_pred = prev.get('next_pred', none) | float(none) %}
            {{ stored_next_pred }}

          csv_thispred: >
            {%- set csv_thispred = states('sensor.gnssNextPred') | float(none) %}
            {%- set csv_thispred = csv_thispred if csv_thispred is not none and csv_thispred > pred_lo and csv_thispred < pred_hi else none %}
            {{ csv_thispred }}

          csv_lastpred: >
            {%- set csv_lastpred = states('sensor.gnssLastPred') | float(none) %}
            {%- set csv_lastpred = csv_lastpred if csv_lastpred is not none and csv_lastpred > pred_lo and csv_lastpred < pred_hi else none %}
            {{ csv_lastpred }}

          rest_thispred_ok: >
            {%- set rest_thispred_ok = nogap and rest_thispred != none and rest_thispred | float > pred_lo and rest_thispred | float < pred_hi %}
            {{ rest_thispred_ok }}

          stored_next_pred_ok: >
            {%- set stored_next_pred_ok = stored_next_pred != none and stored_next_pred | float > pred_lo and stored_next_pred | float < pred_hi %}
            {{ stored_next_pred_ok }}

          thispred: >
            {%- set thispred = rest_thispred | float if rest_thispred_ok else csv_thispred | float if csv_thispred is not none else stored_next_pred | float if stored_next_pred_ok else 0 %}
            {{ thispred }}

          rest_lastpred_ok: >
            {%- set rest_lastpred_ok = nogap and rest_lastpred != none and rest_lastpred | float != 0 and rest_lastpred | float > pred_lo and rest_lastpred | float < pred_hi and (rest_lastpred | float - thispred) | abs > 0.0001 %}
            {{ rest_lastpred_ok }}

          stored_thispred_ok: >
            {%- set stored_thispred_ok = stored_thispred != none and stored_thispred | float != 0 and stored_thispred | float > pred_lo and stored_thispred | float < pred_hi and (stored_thispred | float - thispred) | abs > 0.0001 %}
            {{ stored_thispred_ok }}

          rest_stored_conflict: >
            {%- set rest_stored_conflict = rest_lastpred_ok and stored_thispred_ok and (rest_lastpred | float - stored_thispred | float) | abs > 2.0 %}
            {{ rest_stored_conflict }}

          lastpred: >
            {%- set lastpred = stored_thispred | float if rest_stored_conflict else rest_lastpred | float if rest_lastpred_ok else csv_lastpred | float if csv_lastpred is not none else stored_thispred | float if stored_thispred_ok else thispred %}
            {{ lastpred }}

          lastpred_source: >
            {%- set lastpred_source = 'stored_rest_conflict' if rest_stored_conflict else 'rest' if rest_lastpred_ok else 'csv' if csv_lastpred is not none else 'stored_rest_bad' if stored_thispred_ok else 'fallback_zero_step' %}
            {{ lastpred_source }}

          obs_residual: >
            {%- set obs_residual = thisobs - thispred if nogap else 0 %}
            {{ obs_residual }}

          forcing_pwr_now: >
            {%- set forcing_pwr_now = states('sensor.forcing_pwr') | float(none) %}
            {{ forcing_pwr_now }}

          forcing_pwr_prev: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set forcing_pwr_prev = prev.get('forcing_pwr', none) | float(none) %}
            {{ forcing_pwr_prev }}

          delta_forcing_pwr: >
            {%- set delta_forcing_pwr = (forcing_pwr_now - forcing_pwr_prev) if forcing_pwr_now is not none and forcing_pwr_prev is not none else 0 %}
            {{ delta_forcing_pwr }}

          phi_r: >
            {%- set phi_r = 0.734862406 %}
            {{ phi_r }}

          phi_b: >
            {%- set phi_b = 0.92722028 %}
            {{ phi_b }}

          q_r: >
            {%- set q_r = 0.000407375 %}
            {{ q_r }}

          q_b: >
            {%- set q_b = 0.0000549996 %}
            {{ q_b }}

          spike_gate_2d: >
            {%- set spike_gate_2d = 3.0 %}
            {{ spike_gate_2d }}

          var_decay_2d: >
            {%- set var_decay_2d = 0.062763838 %}
            {{ var_decay_2d }}

          r_floor_2d: >
            {%- set r_floor_2d = 0.075786571 %}
            {{ r_floor_2d }}

          r_cap_2d: >
            {%- set r_cap_2d = 0.215923095 %}
            {{ r_cap_2d }}

          prev_r: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set prev_r = prev.get('state_r', 0.0) | float(0.0) %}
            {{ prev_r }}

          prev_b: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set prev_b = prev.get('state_b', 0.0) | float(0.0) %}
            {{ prev_b }}

          prev_P_r: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set prev_P_r = prev.get('cov_r', 1.0) | float(1.0) %}
            {{ prev_P_r }}

          prev_P_b: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set prev_P_b = prev.get('cov_b', 1.0) | float(1.0) %}
            {{ prev_P_b }}

          prev_gap_count: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set prev_gap_count = prev.get('gap_count', 0) | int(0) %}
            {{ prev_gap_count }}

          prior_adaptive_r: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set prior_adaptive_r = prev.get('adaptive_r', 0.05) | float(0.05) %}
            {{ prior_adaptive_r }}

          needs_reseed: >
            {%- set combined = prev_r + prev_b %}
            {%- set blowup = nogap and (combined - obs_residual) | abs > 2.5 %}
            {%- set recovering = prev_gap_count >= 3 and nogap %}
            {%- set store_missing = states('sensor.gnss15k2da_store') in ['unknown', 'unavailable'] %}
            {%- set needs_reseed = store_missing or blowup or recovering %}
            {{ needs_reseed }}
            
          predicted_r: >
            {%- set predicted_r = obs_residual if needs_reseed else phi_r * prev_r + delta_forcing_pwr %}
            {{ predicted_r }}

          predicted_b: >
            {%- set predicted_b = 0.0 if needs_reseed else phi_b * prev_b %}
            {{ predicted_b }}

          predicted_P_r: >
            {%- set predicted_P_r = 1.0 if needs_reseed else phi_r ** 2 * prev_P_r + q_r %}
            {{ predicted_P_r }}

          predicted_P_b: >
            {%- set predicted_P_b = 1.0 if needs_reseed else phi_b ** 2 * prev_P_b + q_b %}
            {{ predicted_P_b }}

          effective_r: >
            {%- set effective_r = [r_floor_2d, [r_cap_2d, prior_adaptive_r] | min] | max %}
            {{ effective_r }}

          innovation: >
            {%- set innovation = obs_residual - (predicted_r + predicted_b) if nogap else 0 %}
            {{ innovation }}

          status: >
            {%- set innov_std = (predicted_P_r + predicted_P_b + effective_r) ** 0.5 %}
            {%- set threshold = spike_gate_2d * innov_std %}
            {%- set status = 'dead' if feed_dead else 'stale' if feed_stale else 'gap' if not nogap else 'spike' if innovation | abs > threshold else 'OK' %}
            {{ status }}

          clean_innovation: >
            {%- set clean_innovation = innovation if status == 'OK' else 0 %}
            {{ clean_innovation }}

          adaptive_r: >
            {%- set recovering = prev_gap_count >= 3 and status == 'OK' %}
            {%- set raw = 0.05 if last_t is none or recovering else var_decay_2d * clean_innovation ** 2 + (1 - var_decay_2d) * prior_adaptive_r if status == 'OK' else prior_adaptive_r %}
            {%- set adaptive_r = [r_floor_2d, [r_cap_2d, raw] | min] | max %}
            {{ adaptive_r }}

          innov_var: >
            {%- set innov_var = predicted_P_r + predicted_P_b + effective_r %}
            {{ innov_var }}

          K_r: >
            {%- set K_r = predicted_P_r / innov_var if status == 'OK' else 0 %}
            {{ K_r }}

          K_b: >
            {%- set K_b = predicted_P_b / innov_var if status == 'OK' else 0 %}
            {{ K_b }}

          updated_r: >
            {%- set updated_r = predicted_r + K_r * clean_innovation %}
            {{ updated_r }}

          updated_b: >
            {%- set updated_b = predicted_b + K_b * clean_innovation %}
            {{ updated_b }}

          updated_P_r: >
            {%- set updated_P_r = (1 - K_r) ** 2 * predicted_P_r + K_r ** 2 * (predicted_P_b + effective_r) %}
            {{ updated_P_r }}

          updated_P_b: >
            {%- set updated_P_b = (1 - K_b) ** 2 * predicted_P_b + K_b ** 2 * (predicted_P_r + effective_r) %}
            {{ updated_P_b }}

          kalman_raw: >
            {%- set kalman_raw = thispred + updated_r + updated_b %}
            {{ kalman_raw }}

          kalman: >
            {%- set kalman = [pred_lo, [pred_hi, kalman_raw] | min] | max %}
            {{ kalman }}

    sensor:
      - name: gnss15k2da
        unique_id: gnss15k2da
        state: >
          {{ kalman | round(4) }}
        state_class: measurement
        unit_of_measurement: m
        attributes:
          fired_at: "{{ utcnow() | string }}"
          status: "{{ status }}"
          statusnum: >
            {%- set statusnum = 0 if status == 'OK' else 1 if status == 'gap' else 2 if status == 'spike' else 3 if status == 'stale' else 4 if status == 'dead' else 9 %}
            {{ statusnum }}
          kalman: "{{ kalman }}"
          kalman_raw: "{{ kalman_raw | round(4) }}"
          clean_innovation: "{{ clean_innovation | round(4) }}"
          this_t: "{{ this_t }}"
          time: "{{ timeslot }}"
          observed: "{{ thisobs | round(4) }}"
          thispred: "{{ thispred | round(4) }}"
          lastpred: "{{ lastpred | round(4) }}"
          lastpred_source: "{{ lastpred_source }}"
          next_pred: >
            {%- set rest_next = feeds[1].field6 | float(none) if feeds | length > 1 else none %}
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set stored_np = prev.get('next_pred', none) | float(none) %}
            {%- set next_pred = rest_next if nogap and rest_next is not none else stored_np if stored_np is not none else thispred %}
            {{ next_pred }}
          state_r: "{{ updated_r | round(6) }}"
          state_b: "{{ updated_b | round(6) }}"
          cov_r: "{{ updated_P_r | round(6) }}"
          cov_b: "{{ updated_P_b | round(6) }}"
          forcing_pwr: "{{ forcing_pwr_now }}"
          gnssoffset: "{{ (kalman - thispred) | round(4) }}"
          obs_residual: "{{ obs_residual | round(4) }}"
          innovation: "{{ innovation | round(4) }}"
          delta_forcing_pwr: "{{ delta_forcing_pwr | round(4) }}"
          adaptive_r: "{{ adaptive_r | round(6) }}"
          gain_r: "{{ K_r | round(4) }}"
          gain_b: "{{ K_b | round(4) }}"
          needs_reseed: "{{ needs_reseed }}"
          last_t_debug: "{{ last_t }}"
          gap_count: >
            {%- set prev = states.sensor.gnss15k2da_store.attributes if states.sensor.gnss15k2da_store is defined and states.sensor.gnss15k2da_store.attributes is defined else {} %}
            {%- set lastgc = prev.get('gap_count', 0) | int(0) %}
            {%- set gap_count = 0 if status == 'OK' and lastgc >= 3 else [lastgc + (1 if status in ['gap','stale','dead'] else -1 if lastgc > 0 and status == 'OK' else 0), 0] | max %}
            {{ gap_count }}

  - trigger:
      - trigger: time_pattern
        minutes: "09"
      - trigger: time_pattern
        minutes: "24"
      - trigger: time_pattern
        minutes: "39"
      - trigger: time_pattern
        minutes: "54"
    action:
      - delay:
          seconds: 30
      - variables:
          snap_this_t: >
            {{ state_attr('sensor.gnss15k2da', 'this_t') }}
          snap_thispred: >
            {{ state_attr('sensor.gnss15k2da', 'thispred') }}
          snap_next_pred: >
            {{ state_attr('sensor.gnss15k2da', 'next_pred') }}
          snap_state_r: >
            {{ state_attr('sensor.gnss15k2da', 'state_r') }}
          snap_state_b: >
            {{ state_attr('sensor.gnss15k2da', 'state_b') }}
          snap_cov_r: >
            {{ state_attr('sensor.gnss15k2da', 'cov_r') }}
          snap_cov_b: >
            {{ state_attr('sensor.gnss15k2da', 'cov_b') }}
          snap_forcing_pwr: >
            {{ state_attr('sensor.gnss15k2da', 'forcing_pwr') }}
          snap_gap_count: >
            {{ state_attr('sensor.gnss15k2da', 'gap_count') }}
          snap_adaptive_r: >
            {{ state_attr('sensor.gnss15k2da', 'adaptive_r') }}

    sensor:
      - name: gnss15k2da_store
        unique_id: gnss15k2da_store
        state: >
          {{ snap_this_t }}
        attributes:
          fired_at: "{{ utcnow() | string }}"
          this_t: "{{ snap_this_t }}"
          thispred: "{{ snap_thispred }}"
          next_pred: "{{ snap_next_pred }}"
          state_r: "{{ snap_state_r }}"
          state_b: "{{ snap_state_b }}"
          cov_r: "{{ snap_cov_r }}"
          cov_b: "{{ snap_cov_b }}"
          forcing_pwr: "{{ snap_forcing_pwr }}"
          gap_count: "{{ snap_gap_count }}"
          adaptive_r: "{{ snap_adaptive_r }}"

No mentions of gnss15k2da in the log. do you need to see the working one as well?

There’s going to be an error somewhere. The only way for the entity to unload it’s triggers is if there’s an error.

Or, the other possibility is if your code is always producing the last result, which makes it look like it’s not triggering.

You can test out scenario 2 by adding a persistent notification in the action section with the trigger time.

e.g.

- action: persistent_notification.create
  data:
    title: Trigger {{ now().isoformat() }}
    message: Trigger {{ now().isoformat() }}

Indeed. Where do I look if there is nothing in log and it runs in developer tools?

You can only look in the logs. All errors are placed there.

If there’s an exception with a traceback, it may not contain the entity_id. It depends on the exception

I put this edit in:

    action:
      - service: rest_command.getgnss15
        response_variable: gnss
      - action: persistent_notification.create
        data:
          title: Trigger {{ now().isoformat() }}
          message: Trigger {{ now().isoformat() }}

Where would the result of that show up?

as a notification in the sidebar

Got it. So by your analysis it is always producing the last exactly result, including all attributes? One of those is fired_at: "{{ utcnow() | string }}" so why no update?

I didn’t really read all your code, I’m just providing general guidance. Did you confirm that it’s triggering but the entity is not updating?

Yes, I got that notification in the sidebar on the next trigger time

Is it possible that a silent error in the variables block causes it to stop?

Ah ok, then do this after your variables

- action: persistent_notification.create
  data:
    title: Trigger {{ now().isoformat() }}
    message: "Value {{ dict(feeds=feeds, this_t=this_t, timeslot=timeslot, last_t=last_t, feed_stale=feed_stale, feed_dead=feed_dead, nogap=nogap, pred_lo=pred_lo, pred_hi=pred_hi, thisobs=thisobs, rest_thispred=rest_thispred, rest_lastpred=rest_lastpred, stored_thispred=stored_thispred, stored_next_pred=stored_next_pred, csv_thispred=csv_thispred, csv_lastpred=csv_lastpred, rest_thispred_ok=rest_thispred_ok, stored_next_pred_ok=stored_next_pred_ok, thispred=thispred, rest_lastpred_ok=rest_lastpred_ok, stored_thispred_ok=stored_thispred_ok, rest_stored_conflict=rest_stored_conflict, lastpred=lastpred, lastpred_source=lastpred_source, obs_residual=obs_residual, forcing_pwr_now=forcing_pwr_now, forcing_pwr_prev=forcing_pwr_prev, delta_forcing_pwr=delta_forcing_pwr, phi_r=phi_r, phi_b=phi_b, q_r=q_r, q_b=q_b, spike_gate_2d=spike_gate_2d, var_decay_2d=var_decay_2d, r_floor_2d=r_floor_2d, r_cap_2d=r_cap_2d, prev_r=prev_r, prev_b=prev_b, prev_P_r=prev_P_r, prev_P_b=prev_P_b, prev_gap_count=prev_gap_count, prior_adaptive_r=prior_adaptive_r, needs_reseed=needs_reseed, predicted_r=predicted_r, predicted_b=predicted_b, predicted_P_r=predicted_P_r, predicted_P_b=predicted_P_b, effective_r=effective_r, innovation=innovation, status=status, clean_innovation=clean_innovation, adaptive_r=adaptive_r, innov_var=innov_var, K_r=K_r, K_b=K_b, updated_r=updated_r, updated_b=updated_b, updated_P_r=updated_P_r, updated_P_b=updated_P_b, kalman_raw=kalman_raw, kalman=kalman) | to_json }}"

Then watch the results and paste everything in the message after Value into something that formats json and look at all the results.

They should be different every trigger. If they aren’t, somethings likely going on with your rest_command data

I didn’t get a notification at all after putting that at the end of the variables block
I have got error messages like this with no assigned entities

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template/__init__.py", line 465, in async_render
    render_result = render_with_context(self.template, compiled, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template/context.py", line 45, in render_with_context
    return template.render(**kwargs)
           ~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/usr/local/lib/python3.14/site-packages/jinja2/environment.py", line 1295, in render
    self.environment.handle_exception()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/local/lib/python3.14/site-packages/jinja2/environment.py", line 942, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 1, in top-level template code
TypeError: unsupported operand type(s) for +: 'float' and 'str'

You can try this instead:

- action: persistent_notification.create
  data:
    title: Trigger {{ now().isoformat() }}
    message: "Value {{ dict(feeds=feeds, this_t=this_t, timeslot=timeslot, last_t=last_t, feed_stale=feed_stale, feed_dead=feed_dead, nogap=nogap, pred_lo=pred_lo, pred_hi=pred_hi, thisobs=thisobs, rest_thispred=rest_thispred, rest_lastpred=rest_lastpred, stored_thispred=stored_thispred, stored_next_pred=stored_next_pred, csv_thispred=csv_thispred, csv_lastpred=csv_lastpred, rest_thispred_ok=rest_thispred_ok, stored_next_pred_ok=stored_next_pred_ok, thispred=thispred, rest_lastpred_ok=rest_lastpred_ok, stored_thispred_ok=stored_thispred_ok, rest_stored_conflict=rest_stored_conflict, lastpred=lastpred, lastpred_source=lastpred_source, obs_residual=obs_residual, forcing_pwr_now=forcing_pwr_now, forcing_pwr_prev=forcing_pwr_prev, delta_forcing_pwr=delta_forcing_pwr, phi_r=phi_r, phi_b=phi_b, q_r=q_r, q_b=q_b, spike_gate_2d=spike_gate_2d, var_decay_2d=var_decay_2d, r_floor_2d=r_floor_2d, r_cap_2d=r_cap_2d, prev_r=prev_r, prev_b=prev_b, prev_P_r=prev_P_r, prev_P_b=prev_P_b, prev_gap_count=prev_gap_count, prior_adaptive_r=prior_adaptive_r, needs_reseed=needs_reseed, predicted_r=predicted_r, predicted_b=predicted_b, predicted_P_r=predicted_P_r, predicted_P_b=predicted_P_b, effective_r=effective_r, innovation=innovation, status=status, clean_innovation=clean_innovation, adaptive_r=adaptive_r, innov_var=innov_var, K_r=K_r, K_b=K_b, updated_r=updated_r, updated_b=updated_b, updated_P_r=updated_P_r, updated_P_b=updated_P_b, kalman_raw=kalman_raw, kalman=kalman) | string }}"

You’ll have to manually parse the information though.

Still not getting a notification with that at the end of the variables block, so am I safe in saying there is an error in there that stops it proceeding but doesn’t explicitly report to the log?

There would be an exception in your logs, but it wouldn’t point to any entity because that’s before the entity is updated.