Playbook — n8n “atrax fleet sync” failing with 409 / 23505 on p24_l_cars
Workflow: atrax, kravag-scheduled-fleet-updates (id CCx9UMdphmGficDX, bms-4 n8n, 3-min schedule)
Alert: N8nWorkflowPersistentlyFailing · First seen: #1174 (2026-06-23)
Trigger
The workflow’s update-atrax-pojazdy-w car-atrax node (a POST to Supabase
p24_l_cars_atrax?on_conflict=atrax_id) fails every run with NodeApiError /
HTTP 409. The Postgres error is 23505:
duplicate key value violates unique constraint "p24_l_cars_atrax_car_id_unique".
Note: despite the name, this node writes to Supabase, not the Atrax API. The 409 is PostgREST surfacing a Postgres unique-violation, not an Atrax rejection.
Confirmation commands
Pull the exact failing node + error from the n8n execution store on bms-4:
docker exec bms-4-n8n-postgres-1 psql -U n8n -d n8n -t -A -c "
SELECT ed.data FROM execution_data ed
JOIN execution_entity ee ON ee.id=ed.\"executionId\"
WHERE ee.\"workflowId\"='CCx9UMdphmGficDX' AND ee.status='error'
ORDER BY ee.id DESC LIMIT 1;" | grep -o '23505[^}]*}'Find which fleet rows hold a stale atrax_car_id (collision-capable = has_target_row=true):
SELECT c.plates AS fleet_plate, c.atrax_car_id, a.registration_no AS atrax_plate,
EXISTS(SELECT 1 FROM p24_l_cars c2 WHERE c2.plates=a.registration_no) AS has_target_row
FROM p24_l_cars c JOIN p24_l_cars_atrax a ON a.atrax_id=c.atrax_car_id
WHERE c.plates IS DISTINCT FROM a.registration_no
ORDER BY has_target_row DESC;Root cause
A vehicle is re-plated in Atrax (e.g. HVLET442 → BET824). The
merge_atrax_to_fleet() trigger on p24_l_cars_atrax tries to move the
vehicle’s atrax_id onto the fleet row matching the new plate, but the
atrax_id is still attached to the old plate’s row. p24_l_cars.atrax_car_id
is UNIQUE, so the reassignment raises 23505 → 409 → the whole batch upsert
aborts.
Fix (durable — already shipped)
Migration supabase/migrations/20260623_fix_1174_merge_atrax_replate_safe.sql
makes merge_atrax_to_fleet() release a stale atrax_car_id from any other
row before reassigning, guarded by EXISTS(fleet row for current plate) so
cosmetic-only mismatches (*BET561, B ET 633) are never unmapped. Once this
trigger is in place, re-plate cases self-heal on the next scheduled run.
If the trigger is somehow reverted, a one-shot manual unblock:
-- release the stale mapping, then re-fire the trigger
UPDATE p24_l_cars SET atrax_car_id=NULL, gps_atrax_installed=false
WHERE atrax_car_id='<offending_id>' AND plates <> '<current_atrax_plate>';
UPDATE p24_l_cars_atrax SET updated_at=NOW() WHERE atrax_id='<offending_id>';Verify
Wait one schedule cycle (≤3 min) and confirm a success execution:
KEY=$(grep '^BMS4_N8N_API_KEY=' /opt/p24-infra/bms-4/.env | cut -d= -f2- | tr -d '\r')
curl -s -H "X-N8N-API-KEY: $KEY" \
"http://localhost:5678/api/v1/executions?workflowId=CCx9UMdphmGficDX&limit=1" \
| python3 -c "import sys,json;e=json.load(sys.stdin)['data'][0];print(e['status'],e['startedAt'])"Escalation
If 23505 persists with has_target_row=true rows that are NOT a clean re-plate
(e.g. two distinct physical vehicles claiming the same Atrax id), it is a fleet
data-modeling issue — escalate to the fleet/workflow owner; do not auto-merge rows.