* run lpr on more vehicle types by default
before, a config change to attribute_map was required
* logging tweaks
* remove arg
* use attributes for frontend check
* docs
* only check thumbnail attributes the object can have
send_alert()'s short circuit for skipping a no-op "update" push only
compared object and zone counts between before/after, never severity.
Both underlying collections are cumulative and deduplicated (objects
is a set of labels, zones only appends a zone not already present),
so a segment being promoted from detection to alert can leave both
counts unchanged, silently dropping the single most notable
transition in a review's life. Add a severity comparison to the same
check so a detection -> alert promotion always notifies.
The CUDA execution provider returns an identical vector for every image when jina-clip-v2 is built below ORT_ENABLE_EXTENDED, so every thumbnail embedding written on a GPU was the same normalized garbage and semantic search returned the same results for any query. Reproduced on two different NVIDIA cards, across onnxruntime 1.22 and 1.24, and on both the 0.17 and 0.18 CUDA stacks, so it isn't specific to any of those. ORT_ENABLE_ALL isn't an option because it fails to build on CPU with a SimplifiedLayerNormFusion error, leaving EXTENDED as the only level that works on both providers. jinav1 is unaffected and stays on BASIC.
* Make review user read status consistent with other APIs
* Validate URLs for web push endpoint
* Validate the role for a custom viewer, rate limit password changing
* Cleanup
Gemini 3.6 and newer reject role="function" on the function response
Content with 400 INVALID_ARGUMENT, breaking any chat query that triggers
a tool call. The tool call itself succeeds; only the hand-back to the
model fails, and because the error surfaces mid-stream the request still
returns HTTP 200, so it is easy to miss.
Google's function calling documentation specifies role="user" for
returning function results:
contents.append(response.candidates[0].content)
contents.append(types.Content(role="user", parts=[function_response_part]))
https://ai.google.dev/gemini-api/docs/generate-content/function-calling
Verified with my local setup.
* subscribe to add in webpush
* add docs for detector cpu usage
* rebuild notification camera access when a camera is added at runtime
* document how frigate shows CPU usage metrics
* add faq about version key in config
sanitize_filename leaves ".." intact and collapses variants like "..:" and "..*" to "..", so filesystem paths built from face names, classification model/category names, image ids, and trigger data could escape their base directory. Route every such site through new frigate/util/path.py helpers (safe_join, sanitize_path_component, sanitize_contained_path), which reject traversal and verify containment.
Worst case was DELETE /classification/{name}, which rmtree'd /media/frigate and /config while returning 200.
Important to note that all affected endpoints already require admin permission, so this sould be considered hardening rather than fixing exploitable code.
* add host npu requirements to docs
* allow toggling live audio transcription via mqtt
* improve spacing consistency on mobile drawers
* fix clearing the region grid not surviving a restart
* update homekit docs
* update dictionary
* preserve function names in production builds
adds only 162kb gzipped/450k unzipped to the bundle
* margin tweak
* fix maximum update depth exceeded when dragging the timeline handlebar
Dragging the handlebar, especially quickly or with fast direction changes, could exceed React's nested update limit and unmount the whole app, leaving a blank screen. Motion search was worst affected.
The drag loop committed a new time into React state on every animation frame. Edge auto-scrolling mutates scrollTop each iteration, so the value always differed and React's same-value bail-out never engaged, letting the update chain run to the limit of 50. Pace those commits to one per 100ms and flush the pending value on release, so the drop position is still exact. The handlebar position and label are written to the DOM directly and remain at frame rate.
useUserInteraction dispatched state on every scroll and touchmove event; only commit on the leading edge.
Motion search also passed fresh array literals for the timeline's events, motion events and unavailable ranges, giving the segment memo and the drag effect new dependencies on every render. Both views also passed an inline arrow for onHandlebarDraggingChange, which is an effect dependency that calls setState.
* Verify motion search jobs belong to the requested camera
* Apply persisted profile and runtime overrides before workers start
Worker processes are handed a copy of the config when they start and only learn about later changes from the config_updater broadcast, which is plain ZMQ PUB/SUB with no queue, ack, or retained value, so a message published before a subscriber has connected is dropped and never re-sent. The persisted profile and the runtime camera toggles were restored only by that broadcast, at the very end of startup, so a worker that lost the race kept its yaml values for the rest of the session: audio detection kept running on a camera whose audio had been toggled off, even though /api/config, the UI, and the runtime state file all showed it disabled. Split both restores into a config half and a publish half. ProfileManager.restore_persisted_profile_to_config() and Dispatcher.reapply_runtime_state_to_config() now run right after init_profile_manager(), before the first worker starts, so every worker is handed a config that already carries both layers. ProfileManager.restore_persisted_profile() and Dispatcher.restore_runtime_state() still run at the end of startup: the recording, review, and embeddings processes start before the dispatcher exists, so the broadcast remains their only channel, and MQTT needs the retained switch states. Both config passes have to stay after init_profile_manager(), which snapshots the config as the no-profile base that deactivation resets to.
* End timeline drags on touchcancel
/auth grants anonymous admin to any request whose X-Server-Port matches networking.listen.internal, but it read that port off the live config while nginx binds its listeners once at container start and never reloads them, so any path that swaps the running config could move the trusted port without nginx moving with it. Saving networking.listen.internal equal to the external port applied immediately despite the restart-required warning, which handed unauthenticated admin to everything reaching the external port. Snapshot the port at app creation and compare against that instead, and reject a config whose two listeners share a port number, which nginx would refuse to start with anyway.
* update network requirements docs for keras weights download
* fix manual PTZ relative moves permanently stopping object detection
* document available camera set features and link profiles docs to the API
* fix stale stream name field when switching cameras
The live streams and known plates fields rendered the map key as an uncontrolled input, so switching cameras left the previous camera's stream name on screen and would rename the wrong key if that stale text was committed. Both now use a shared MapKeyInput that resyncs with the form data and commits per keystroke, except while the typed name belongs to another entry, so the section is marked modified without waiting for blur.
* improve display of gpu graphs in system metrics
* docs tweaks
* Only hide cameras with ui.dashboard disabled from the All Cameras dashboard
The settings camera selector and zone editor also filtered on ui.dashboard, so hiding a camera from the dashboard made its zones and masks uneditable in the UI (GH 23870). Drop those filters and correct the field title, help text, and reference docs to describe what the option actually does
* hide cameras with ui.review disabled from the Motion tab and the review summaries
The Motion tab built its own camera list that never checked ui.review, so a hidden camera still got a preview tile, and its motion and overlap queries fell back to every allowed camera. The review and recordings summaries had the same gap: they are aggregate day counts that can't be filtered client side, so a hidden camera kept contributing to the severity tab counts and calendar indicators while its items were absent from the list. Filter the motion camera list on ui.review and query all four endpoints with the visible camera list instead of letting the backend default to all, and skip the summary queries until the config resolves so the counts don't briefly render as zero.
* Scope every review page query to the cameras visible in review
The segments and the summary counts were derived from different camera sets: the list was fetched for all cameras and filtered client side, while the summaries were fetched for the visible cameras only when no explicit camera filter was set. A ?cameras= link can name a camera hidden from review, which left the count above zero with an empty list, pinning the new items to review popover open and making the auto refresh effect loop. Intersect an explicit camera selection with the visible list rather than trusting it, pass that to the segment and summary queries alike, and drop the now redundant client side filter, which the raw segments handed to the history view were bypassing anyway.
* fix watchdog process restarts reverting to the boot config
/api/config/set parses a new FrigateConfig and swaps the API and dispatcher onto it, but FrigateApp.config was never rebound, so the watchdog factories rebuilt a crashed process from the config as of startup. Fix is to read through a ConfigHolder that the swap updates.
* fix birdseye camera overrides being clobbered by a global mode change
A global birdseye save published only the global object, leaving the output process to infer which cameras were inheriting by comparing against the previous global mode. That cannot tell an inherited value from an explicit one that happens to match, so it overwrote the override until a restart. Publish the per-camera values the config parse already resolved instead.
* Reject non-finite numbers in GenAI review descriptions
A model returning NaN for confidence or potential_threat_level slipped through the model_construct fallback, which skips validation, and was written into the review segment's JSON data. NaN is not valid JSON, so every subsequent /review request failed with "Out of range float values are not JSON compliant", blanking the review page for any time range containing the poisoned row.
* restore fused DetectionOutput in the OpenVINO SSD model conversion
* fix rgb swap issue for face dataset testing script
* widen the logger name field in the per-process log level settings
* add details to timestamp error faq
* tweak genai docs
* tweak vector language
* Combine Qwen3.5 and Qwen3.6 listings
* fix openvino yolox detector crashing on every detection
The intermediate (N, 7) array in the yolox branch shadowed the pre-allocated (20, 6) detections buffer, so writing a detection into it raised "could not broadcast input array from shape (6,) into shape (7,)" on the first frame with anything above the confidence threshold. An empty frame also returned a (0, 7) array instead of the (20, 6) buffer.
Regressed in #13794, which renamed the intermediate from dets to detections as part of a cspell cleanup. Broken since 0.15.0.
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
* recreate review thumbnail directory before writing and log write failures
cleanup's remove_empty_directories() can rmdir an empty clips/review, after which thumbnail writes silently fail. Ensure the directory exists before both cv2.imwrite calls and check their return value
* add docs for add camera wizard
* Handle indefinite events when a segment needs to forcibly be ended for a ceamera
* update keyframe interval article link
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
* Recreate preview output directory before writing it
The preview directory is created once in PreviewRecorder.__init__, but
record cleanup's remove_empty_directories() can delete it again while it
is empty (e.g. a camera re-added after removal, or an hour with no
retained previews). FFMpegConverter then fails permanently with
"No such file or directory" and previews are silently lost with only
one ERROR log line per hour. Recreate the directory before invoking
ffmpeg so the hourly export self-heals.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Remove explanatory comments above the fix
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* resolve saved credential sentinel to the stored api_key in the GenAI probe
* add profile faq
* center the multi-camera export time range on the current playback position
* add faq about preview restart cache
* clarify exports bulk download
* Catch faces that become empty after cropping
* don't drop batched camera add/remove config updates
TrackedObjectProcessor drained all pending camera config updates at once but handled them in a mutually exclusive if/elif on enabled/add/remove, so only one topic was processed per drain. When an add arrived in the same batch as an enabled update, the add was skipped and the new camera never got a camera state. Adding a camera reliably produced that batch: config_set now re-applies runtime overrides, which republishes an enabled update for every previously toggled camera immediately before the add, in the same request. The dashboard and camera capture still saw the camera (the maintainer does not subscribe to enabled, so it got a clean add-only batch), but object_processing did not, and disabling the camera then crashed with a KeyError on the unguarded camera_states lookup.
Handle add and remove independently instead of as exclusive branches so a batched add is no longer dropped, and guard the remove lookup so a missing state is skipped rather than raising. Drop the enabled branch entirely: it only ever set prev_enabled when it was None, but prev_enabled is seeded to a bool at camera state creation and is never None (mypy flags the body as unreachable), and the actual enable/disable transition is already driven by the disabled-state loop from config.enabled.
* Don't stay on motion search page when user cancels flow
* fix notification test button being blocked by websocket auth
* fix overflowing model names in settings genai widget
* add note about auth debugging
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
/api/config/set and camera deletion re-parse yaml into a fresh FrigateConfig and swap it in, then re-layered the persisted runtime toggle overrides so a camera the user turned off wouldn't come back on. That re-layer ran apply_runtime_state, which replays each override through the command handlers, so every save re-published a ZMQ config update, a retained MQTT state message, and a runtime-state disk write for every camera with a stored toggle. All of it was redundant: the worker processes were never swapped and still hold the live toggle values, so only the in-process config object the API and dispatcher read was out of date. The extra traffic churned the retained MQTT topics, amplified disk writes, and co-drained enabled updates with other topics on the config socket.
Add Dispatcher.reapply_runtime_state_to_config, which corrects only the swapped-in config object, mirroring the field mutations and gates of the _on_*_command handlers with no ZMQ, MQTT, or disk writes. swap_runtime_config now calls it instead of apply_runtime_state; apply_runtime_state is unchanged and still used at startup, where the workers genuinely must be told.
* preserve runtime camera toggles across config saves
Runtime toggles (camera on/off, detect, recordings, snapshots, audio) mutate the in-memory config and persist an override to .runtime_state.json. /api/config/set re-parses yaml into a fresh FrigateConfig and swaps it in, re-applying the yaml and profile layers but dropping the runtime layer, so a camera turned off from the dashboard came back on when an unrelated camera was saved. The workers were never notified, so it only appeared to come back: the UI streamed go2rtc while ffmpeg stayed stopped.
Extract the startup replay into Dispatcher.apply_runtime_state() and call it from config_set after the swap, re-layering the overrides and republishing them so workers and the UI reconverge.
Remove the broad clear_runtime_state() from ProfileManager.update_config, which is only ever reached from config_set: with a profile active, every save wiped every camera's overrides from disk. The broad wipe stays in activate_profile, where a real profile switch does invalidate the steady state. Saves still clear the keys they rewrote via clear_runtime_state_for_yaml_keys, so yaml wins where the two disagree.
* sync runtime config on camera delete and prune its overrides
Deleting a camera re-parsed yaml into a fresh FrigateConfig but only rebound app.frigate_config and genai_manager, never dispatcher.config (nor profile_manager, stats_emitter, or the runtime overrides). The API and the dispatcher then drifted onto different config objects until the next config save re-synced them, so the API reported surviving cameras with their yaml enabled state while the dispatcher still acted on their real runtime state.
Extract the config swap that config_set already does into a shared swap_runtime_config helper and call it from both sites, so every collaborator is rebound and the surviving cameras' runtime toggles are re-layered. Also drop the deleted camera's persisted overrides via a new clear_camera so a camera later added under the same name does not inherit them.
* Cleanup llama.cpp and use api key when configured
* don't report auto-populated object and audio filters as camera overrides
* derive stale replay cameras from bounded directory listings to avoid scanning all clips at startup
* fix tests
* add -vaapi_device to the birdseye vaapi encode preset so hwupload can initialize on ffmpeg 8
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
* resolve zone friendly names against the correct camera
* Improve handling of zone names in chat prompt
* show a numeric keyboard for numeric config form fields on mobile
* Specify english only for semantic search tool when model is JinaV1
* resolve export hwaccel args global value against the correct config path
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
The recognized_license_plate event filter passed attacker-controlled patterns to re.search on the single serialized SQLite queue thread, letting any authenticated user freeze the whole application with a catastrophic regex. This swaps stdlib re for the regex module with a per-evaluation timeout so a pathological pattern is aborted instead of stalling every database operation.
* Add logout endpoint to Nginx configuration to prevent logout from silently generating a new frigate_token cookie
* Change JWT cookie expiration to use max_age and have the appropriate expiration time based on JWT_SESSION_LENGTH
* ruff formatting
* Convert face crops to RGB before embedding
Face crops flow through the cv2 pipeline as BGR arrays, but
_process_image passes ndarrays to PIL without any channel conversion,
so the FaceNet and ArcFace embedders receive BGR input while both
models expect RGB. The error is symmetric between enrollment and
recognition so it partially cancels, but it still costs accuracy.
* Move BGR to RGB conversion into a shared helper
Deduplicate the channel swap from both _preprocess_inputs methods
into a BaseEmbedding._bgr_to_rgb static helper, as suggested in
review.
* add ability to edit enabled and save_attempts for classification models in the UI
* add state motion and interval configs to edit dialog
* fix preview playback rate for motion previews
* add docs note about environment vars and go2rtc
* update live view faq
* honor enabled flag for custom classification models
for both startup and dynamically, even though the UI doesn't currently have a way to toggle dynamically
* add test
* Pin ruff
* Add python upgrade fixes
This enables python upgrade checks in ruff to look for deprecated types and patterns. This namely fixes:
- usage of deprecated `Typing` which is now built in
- some specific exceptions which are caught and have new aliases
Some specific UP checks were also ignored as they are stylistic / unimportant and likely to cause bugs
* Remove async blocking calls
Use asyncio.to_thread on two remaining blocking calls to fix hanging event thread loop. Enable this specific rule to block it in the future.
* Use proper logging mechanism
* Correctly format logs
* Raise with context
When raising an exception include the from context to improve debugging
* Cleanup
* fix per-camera notification MQTT topics never being registered
register notifications/set and notifications/suspend callbacks for each camera, and gate the global notifications topics on per-camera config as well as global (matching WebPushClient creation in app.py). Unregistered topics were silently dropped by paho since only registered callbacks receive messages.
* add tests
Deleting the last entry of a mapping or sequence via config/set orphaned ruamel's comment tokens, which were then emitted above a flow-style {} / [] at column 0, which is unparseable yaml that failed validation and silently rolled the change back. The fix is to clear the emptied collection's stale comment metadata (and the parent's entry for it) so the dump stays valid. Non-empty collections are left untouched so sibling comments are preserved. This covers both emptied maps and emptied lists.
* fix stale active object indicators on the live dashboard
The camera_activity/<camera> snapshot cache is only written when a client sends onConnect, and object "end" events only update the local state of mounted useCameraActivity hooks, never the cache. As a result, a hook that seeded from a stale cache or missed an "end" event while disconnected showed objects that had already left, with no path to correct itself short of a full page reload.
This change will re-request the snapshot on hook mount (collapsed to one onConnect per task across camera cards), and always re-notify camera_activity topics so hooks reconcile against their own local state instead of relying on snapshot-vs-snapshot comparison, and clear the payload dedup cache on reconnect and resync so byte-identical snapshots still apply.
* docs tweaks
* fix mqtt log message
* use consistent values for lpr debug frame filenames
with millisecond resolution
* apply object events through a functional updater to prevent lost updates
The events effect derived a new objects list from the value captured at render time and wrote the whole list back. When events arrived close
together, a run derived from a stale list erased a concurrent run's removal; the resurrected object then had no remaining "end" event to clear it, and the add branch could mint a duplicate entry that no splice could ever remove, leaving the live dashboard showing active objects the backend had already cleared, until a page reload.
The fix is to apply each event inside setObjects so it operates on the true current list exactly once. Unchanged results return the same reference so React bails out of re-rendering, and the label rewrite is hoisted so added objects get the sub_label/verified label directly instead of relying on the effect re-running against its own state update.