Merged
Conversation
|
stavros-k
approved these changes
Apr 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
2026.04.13→2026.04.162026.4.14→2026.4.15v10.3.3→v10.4.0v2.2.0→v2.3.02.9.7→2.9.84.115.0→4.116.00.22.1→0.23.0v1.6.3→v1.6.40.24.1608→0.24.16141.18.0→1.18.15.0.1→5.0.2v5.0.6→v5.0.73.0.3→3.1.0v2.27.0→v2.27.11.684.1→1.685.013.0.0→13.0.1apache-2.54.0→apache-2.55.00.15.2→0.15.34.52.5→4.53.28.2.6→8.2.70.20.7-rocm→0.21.0-rocm0.20.7→0.21.00.22.6-pg18→0.23.0-pg182026.4.16-ae0b0e56a→2026.4.17-8579974f5Release Notes
openclaw/openclaw (alpine/openclaw)
v2026.4.15Compare Source
Changes
opusaliases, Claude CLI defaults, and bundled image understanding to Claude Opus 4.7.googleplugin, including provider registration, voice selection, WAV reply output, PCM telephony output, and setup/docs guidance. (#67515) Thanks @barronlroth.models.authStatusgateway method that strips credentials and caches for 60s. (#66211) Thanks @omarshahine.memory-lancedbso durable memory indexes can run on remote object storage instead of local disk only. (#63502) Thanks @rugvedS07.agents.defaults.experimental.localModelLean: trueto drop heavyweight default tools likebrowser,cron, andmessage, reducing prompt size for weaker local-model setups without changing the normal path. (#66495) Thanks @ImLukeF.qa-matrixrunner and keep repo-privateqa-*surfaces out of packaged and published builds. (#66723) Thanks @gumadeiras.Fixes
MEDIA:tool-result passthrough on the exact raw name of this run's registered built-in tools, and reject client tool definitions whose names normalize-collide with a built-in or with another client tool in the same request (400 invalid_request_erroron both JSON and SSE paths), so a client-supplied tool named like a built-in can no longer inherit its local-media trust. (#67303)401 input item ID does not belong to this connectionas replay-invalid, so users get the existing/newsession reset guidance instead of a raw 401-style failure. (#66475) Thanks @dallylee.@matrix-org/matrix-sdk-crypto-nodejsnative bindings withfindundernode_modulesinstead of a hardcoded.pnpm/...path so pnpm v10+ virtual-store layouts no longer fail the image build. (#67143) thanks @ly85206559.channels.matrix.password, and document the remaining password-UIA limitation. (#66228) Thanks @SARAMALI15792.NO_REPLYso trailing silent sentinels no longer leak summary text to the target channel. (#65004) thanks @neo1027144-creator.OPENCLAW_BUNDLED_PLUGINS_DIRflips stop reusing stale plugin, setup, secrets, and runtime state. (#67200) Thanks @gumadeiras.memory_getexcerpts by default with explicit continuation metadata, and keep QMD reads aligned with the same bounded excerpt contract so long sessions pull less context by default without losing deterministic follow-up reads.agents.defaults.contextTokensis the real limit. (#66236) Thanks @ImLukeF.dreaming.storage.modefrominlinetoseparateso Dreaming phase blocks (## Light Sleep,## REM Sleep) land inmemory/dreaming/{phase}/YYYY-MM-DD.mdinstead of being injected intomemory/YYYY-MM-DD.md. Daily memory files no longer get dominated by structured candidate output, and the daily-ingestion scanner that already strips dream marker blocks no longer has to compete with hundreds of phase-block lines on every run. Operators who want the previous behavior can opt in by settingplugins.entries.memory-core.config.dreaming.storage.mode: "inline". (#66412) Thanks @mjamiv.<function>...</function>tool-call payloads from visible assistant text without truncating prose examples or trailing replies. (#67318) Thanks @joelnishanth.creds.jsonwrites and falsely restores from backup. (#67464) Thanks @neeravmakwana.catchup.maxFailureRetries, default 10) so a persistently-failing message with a malformed payload no longer wedges the catchup cursor forever. After N consecutiveprocessMessagefailures against the same GUID, catchup logs a WARN, skips that message on subsequent sweeps, and lets the cursor advance past it. Transient failures still retry from the same point as before. Also fixes a lost-update race in the persistent dedupe file lock that silently dropped inbound GUIDs on concurrent writes, a dedupe file naming migration gap on version upgrade, and a balloon-event bypass that let catchup replay debouncer-coalesced events as standalone messages. (#67426, #66870) Thanks @omarshahine.ollama/provider prefix from Ollama chat request model ids so configured refs likeollama/qwen3:14b-q8_0stop 404ing against the Ollama API. (#67457) Thanks @suboss87.~/...host edit/write operations stop failing or reading back the wrong file whenOPENCLAW_HOMEdiffers. (#62804) Thanks @stainlu.[[tts:speed=1.2]]stop silently landing on the wrong provider. (#62846) Thanks @stainlu.openai-codexrows with missingapiorhttps://chatgpt.com/backend-api/v1self-heal to the canonical Codex transport instead of routing requests through broken HTML/Cloudflare paths, combining the original fixes proposed in #66969 (saamuelng601-pixel) and #67159 (hclsys). (#67635)skills.*(for exampleskills.allowBundled,skills.entries.<id>.enabled, orskills.profile). Existing agent sessions persist askillsSnapshotinsessions.jsonthat reuses the skill list frozen at session creation; without this invalidation, removing a bundled skill from the allowlist left the old snapshot live and the model kept calling the disabled tool, producingTool <name> not foundloops that ran until the embedded-run timeout. (#67401) Thanks @xantorres.resolveUnknownToolGuardThresholdreturnedundefinedunlesstools.loopDetection.enabledwas explicitly set totrue, which left the protection off in the default configuration. A hallucinated or removed tool (for examplehimalayaafter it was dropped fromskills.allowBundled) would then loop "Tool X not found" attempts until the full embedded-run timeout. The guard has no false-positive surface because it only triggers on tools that are objectively not registered in the run, so it now stays on regardless oftools.loopDetection.enabledand still acceptstools.loopDetection.unknownToolThresholdas a per-run override (default 10). (#67401) Thanks @xantorres.tui-event-handlersso thestreaming · Xm Ysactivity indicator resets toidleafter 30s of delta silence on the active run. Guards against lost or latestate: "final"chat events (WS reconnects, gateway restarts, etc.) leaving the TUI stuck onstreamingindefinitely; a new system log line surfaces the reset so users know to send a new message to resync. The window is configurable via the newstreamingWatchdogMscontext option (set to0to disable), and the handler now exposes adispose()that clears the pending timer on shutdown. (#67401) Thanks @xantorres.(baseUrl, modelKey, contextLength)tuple with a 5s → 10s → 20s → … → 5min cooldown and skips the preload step entirely while a cooldown is active, letting chat requests proceed directly to the stream (the model is often already loaded via the LM Studio UI). The combinedpreload failedlog line now reports consecutive-failure count and remaining cooldown so operators can act on the real issue instead of drowning in repeated warnings. (#67401) Thanks @xantorres....toolresult1during compaction and retry flows. (#67620) Thanks @stainlu.codexis selected as an embedded agent harness runtime, including forced default, per-agent, andOPENCLAW_AGENT_RUNTIMEpaths. (#67474) Thanks @duqaXxX.codex exec resumeruns on the safe non-interactive path without reintroducing the removed dangerous bypass flag by passing the supported--skip-git-repo-checkresume arg plus Codex's nativesandbox_mode="workspace-write"config override. (#67666) Thanks @plgonzalezrx8.Codex Desktop/0.118.0, keeping the version gate working when the Codex CLI inherits a multi-word originator. (#64666) Thanks @cyrusaf.NO_REPLYstripping case-insensitive across direct and text delivery, preserve structured media-only sends when a caption strips silent, and derive main-session awareness from the cleaned payloads so silent captions no longer leak staleNO_REPLYtext. (#65016) Thanks @BKF-Gitty.delivery-mirrortranscript appends only when the latest assistant message has the same visible text, preventing duplicate visible replies on Codex-backed turns without suppressing repeated answers across turns. (#67185) Thanks @andyylin.updated-messagewebhooks carrying attachments, use event-type-aware dedup keys so attachment follow-ups are not rejected as duplicates, and retry attachment fetch from the BB API when the initial webhook arrives with an empty array. (#64105, #61861, #65430, #67510) Thanks @omarshahine.available_skillsentries by skill name after merging sources soskills.load.extraDirsorder no longer changes prompt-cache prefixes. (#64198) Thanks @Bartok9.models.providers.*.models.*.compat.supportsPromptCacheKeyso OpenAI-compatible proxies that forwardprompt_cache_keycan keep prompt caching enabled while incompatible endpoints can still force stripping. (#67427) Thanks @damselem.afterTurnprompt-cache touch metadata aligned with the current assistant turn so cache-aware context engines retain accurate cache TTL state during tool loops. (#67767) thanks @jalehman.distchunks after npm upgrades and keep downgrade/verify inventory checks compat-safe so global upgrades stop failing on stale chunk imports. (#66959) Thanks @obviyus.memory_get: reject reads of arbitrary workspace markdown paths and only allow canonical memory files (MEMORY.md,memory.md,DREAMS.md,dreams.md,memory/**) plus exact paths of active indexed QMD workspace documents, so the QMD memory backend can no longer be used as a generic workspace-file read shim that bypassesreadtool-policy denials. (#66026) Thanks @eleqtrizit.--toolsallowlists, cron-owned message-tool suppression, explicit message targeting, and command-path internal events all take effect at runtime again. (#62675) Thanks @hexsprite.Cannot read properties of undefined (reading 'trim'). (#66649) Thanks @Tianworld.mxc://avatar URLs, and surface gmail watcher stop failures during reload. (#64701) Thanks @slepybear..mobior.epubno longer explode prompt token counts. (#66663) Thanks @joelnishanth.getResolvedAuth(), mirroring the WebSocket path, so a secret rotated throughsecrets.reloador config hot-reload stops authenticating on/v1/*,/tools/invoke, plugin HTTP routes, and the canvas upgrade path immediately instead of remaining valid on HTTP until gateway restart. (#66651) Thanks @mmaps.Unknown error (no error details in response)transport failure as failover reasonunknownso assistant/model fallback still runs for that no-details failure path. (#65254) Thanks @OpenCodeEngineer.formatinstead ofunknowninmodels list --probe, and lock the invalid-model fallback path in with regression coverage. (#50028) Thanks @xiwuqi.finish_reason: network_errorstream failures as timeout so model fallback retries continue instead of stopping with an unknown failover reason. (#61784) thanks @lawrence3699./verbosewhen Slack renders native buttons by giving each button a unique action ID while still routing them through the sharedopenclaw_cmdarg*listener. Thanks @Wangmerlyn.encryptKeyand blank callback tokens — refuse to start the webhook transport without anencryptKey, reject unsigned requests when no key is present instead of accepting them, and drop blank card-action tokens before the dedupe claim and dispatcher. Defense-in-depth over the already-closed monitor-account layer. (#66707) Thanks @eleqtrizit.agents.files.get,agents.files.set, and workspace listing through the sharedfs-safehelpers (openFileWithinRoot/readFileWithinRoot/writeFileWithinRoot), reject symlink aliases for allowlisted agent files, and havefs-saferesolve opened-file real paths from the file descriptor before falling back to path-basedrealpathso a symlink swap betweenopenandrealpathcan no longer redirect the validated path off the intended inode. (#66636) Thanks @eleqtrizit./mcpbearer comparison from plain!==to constant-timesafeEqualSecret(matching the convention every other auth surface in the codebase uses), and reject non-loopback browser-origin requests viacheckBrowserOriginbefore the auth gate runs. Loopback origins (127.0.0.1:*,localhost:*, same-origin) still go through, including thelocalhost↔127.0.0.1host mismatch that browsers flag asSec-Fetch-Site: cross-site. (#66665) Thanks @eleqtrizit.max_tokensvalues no longer reach the provider API. (#66664) thanks @jalehman.epuband.mobiuploads can no longer leak raw binary into prompt context through reply metadata or archive-to-text/plaincoercion. (#66877) Thanks @martinfrancois.commands.nativeandcommands.nativeSkillsstay onauto. (#66843) Thanks @kashevk0.reasoning_detailsstream deltas as thinking content without skipping same-chunk tool calls, so Qwen3 replies no longer fail empty on OpenRouter and mixed reasoning/tool-call chunks still execute normally. (#66905) Thanks @bladin./api/v1/message/query?after=<ts>pass, so messages delivered while the gateway was down no longer disappear. Uses the existingprocessMessagepath and is deduped by #66816's inbound GUID cache. (#66857, #66721) Thanks @omarshahine.models.providers.*.request.allowPrivateNetworkfor audio transcription so private or LAN speech-to-text endpoints stop tripping SSRF blocks after the v2026.4.14 regression. (#66692) Thanks @jhsmith409.event.contentinparseFaceTagsandfilterInternalMarkersso cron-triggered agent turns with no content payload no longer crash withTypeError: Cannot read properties of undefined (reading 'startsWith'). (#66302) Thanks @xinmotlanthua.--dangerously-force-unsafe-installplugin installs from falling back to hook-pack installs after security scan failures, while still preserving non-security fallback behavior for real hook packs. (#58909) Thanks @hxy91819.No conversation found with session IDassession_expiredso expired CLI-backed conversations clear the stale binding and recover on the next turn. (#65028) thanks @Ivan-Fn..csvor.mdslip past the host-read guard. (#67047) Thanks @Unayung.Cloud + Local,Cloud only, andLocal only, support directOLLAMA_API_KEYcloud setup without a local daemon, and keep Ollama web search on the local-host path. (#67005) Thanks @obviyus.file://URLs in the media embedding path. (#67293) Thanks @pgondhi987.dailyCountacross days instead of stalling at1. (#67091) Thanks @Bartok9./usr/bin/whoamino longer get rejected as unsafe interpreter/runtime commands. (#66731) Thanks @tmimmanuel.amir20/dozzle (amir20/dozzle)
v10.4.0Compare Source
🚀 Features
View changes on GitHub
Dispatcharr/Dispatcharr (ghcr.io/dispatcharr/dispatcharr)
v0.23.0Compare Source
Security
DEFAULT_PERMISSION_CLASSEStoIsAdminin the DRF configuration. All viewsets and function-based views that require non-admin or unauthenticated access were explicitly annotated: proxy streaming endpoints (stream_ts,stream_xc,stream_vod,head_vod,stream_xc_movie,stream_xc_episode) use@permission_classes([AllowAny])(access is controlled by the per-stream-type network allow-list inside the view body); theUserAgentViewSet,StreamProfileViewSet,CoreSettingsViewSet, andProxySettingsViewSetgainedget_permissions()methods mapping read actions toIsStandardUserand write actions toIsAdmin; andAuthViewSet.logoutwas updated to return[Authenticated()].network_access_allowedchecks in the VOD proxy.stream_vod,head_vod,stream_xc_movie, andstream_xc_episodewere not checking theSTREAMSnetwork policy, unlike the equivalent TS proxy endpoints.DiscoverAPIView,LineupAPIView,LineupStatusAPIView,HDHRDeviceXMLAPIView) and the version endpoint withpermission_classes = [AllowAny]to document their intentionally public access now that the global default isIsAdmin.apps/m3u/api_views.py), logo upload (apps/channels/api_views.py), and backup upload (apps/backups/api_views.py) all used the uploaded filename directly without sanitization.os.path.join()discards all preceding components when it encounters an absolute path segment, andpathlib's/operator behaves identically; a relative../sequence also escapes via OS path resolution atopen()time. All three upload paths now strip directory components viaPath(name).nameand validate the resolved path remains within the intended upload directory. Exploiting any of these required admin credentials.xc_password(and other admin-managed keys) on their own account via thePATCH /api/accounts/users/me/endpoint.change_streamendpoint by converting it from a plain Django view to a DRF@api_viewwith@permission_classes([IsAdmin]), ensuring the endpoint actually enforces admin-only access. The previous decorator arrangement (@csrf_exempt+@permission_classes) had no effect on a plain Django view.POST /api/accounts/token/) using DRF's built-in throttling. ALoginRateThrottle(3 requests/minute per IP, sliding window) is applied to theTokenObtainPairView. Repeated failed attempts from the same IP receive429 Too Many Requests.POST /api/accounts/auth/login/). It now delegates entirely toTokenObtainPairView, inheriting its throttle, network access check, and audit logging, and returns JWT tokens instead of a session cookie (the session-based response was unusable sinceSessionAuthenticationis not inDEFAULT_AUTHENTICATION_CLASSES). Both endpoints share the same"login"throttle scope, so attempts across either path count against the same per-IP limit.CORS_ALLOW_CREDENTIALS = Truefrom CORS configuration. Dispatcharr authenticates via JWTAuthorizationheaders and API keys — not cookies — so credentials are never sent cross-origin by browsers. The setting was also redundant: browsers rejectAccess-Control-Allow-Credentials: truewhenAccess-Control-Allow-Originis a wildcard (*), so it had no effect in practice.@xmldom/xmldom0.8.11 → 0.8.12, resolving high XML injection via unsafe CDATA serialization allowing attacker-controlled markup insertion (GHSA-wh4c-j3r5-mjhp)lodash4.17.23 → 4.18.1, resolving high Code Injection via_.templateimports key names (GHSA-r5fr-rjxr-66jc) and high Prototype Pollution via array path bypass in_.unsetand_.omit(GHSA-f23m-r3pf-42rh)vite7.3.1 → 7.3.2, resolving high Path Traversal in optimized deps.maphandling (GHSA-4w7w-66w2-5vf9), highserver.fs.denybypass with queries (GHSA-v2wj-q39q-566r), and high Arbitrary File Read via dev server WebSocket (GHSA-p9ff-h696-f583)Django6.0.3 → 6.0.4, resolving the following CVEs:MultiPartParserthrough crafted multipart uploads.Content-Lengthheader could bypass theDATA_UPLOAD_MAX_MEMORY_SIZElimit.ModelAdmin.list_editable.GenericInlineModelAdmin.Added
prev_daysURL parameter (e.g.&prev_days=3) to include past programs in the EPG response. This allows third-party players that request historical program schedules to receive the data they need. The EPG URL builder in the Channels page exposes "Days forward" and "Days back" controls. Per-user defaults for both values (epg_days/epg_prev_days) can be configured in the User settings modal and are applied automatically when no URL parameter is present. (Closes #1154)min_dispatcharr_version/max_dispatcharr_version) are enforced at install time.Removed
VODConnectionManagerclass (apps/proxy/vod_proxy/connection_manager.py) and its associated helpers, which had been superseded byMultiWorkerVODConnectionManager. All active code already used the multi-worker implementation. Removed the unusedVODConnectionManagerimport fromvod_proxy/views.py, the unscheduledcleanup_vod_connectionstask fromapps/proxy/tasks.py, and the unscheduledcleanup_vod_persistent_connectionstask fromcore/tasks.py.VODPlaylistView(playlist generation),VODPositionView(position tracking), and the class-basedVODStatsView(replaced by the existing function-basedvod_statsview).updateVODPosition()API method fromfrontend/src/api.js, which called the now-removed position tracking endpoint.Fixed
tvg_id, rather than the channel the user actually selected. When multiple channels share the same EPG source, the intended channel was silently ignored. The selected channel object is now passed explicitly through the click handler chain torecordOne, bypassing thefindChannelByTvgIdfallback lookup entirely. (Fixes #1140) — Thanks @fezsterdocker stopno longer results in exit 137 (SIGKILL). The entrypoint now explicitly stops all child processes — including uWSGI workers, Celery, Daphne, and Redis, which are spawned as uWSGIattach-daemonchildren and were previously invisible to the signal handler. A polling loop replaces the old fixedsleep, exiting as soon as all processes have stopped (up to an 8-second ceiling before force-stopping). PostgreSQL is stopped usingpg_ctl stop -m immediateas a fallback rather than SIGKILL to avoid data corruption. Process names are now recorded at startup and displayed correctly in crash diagnostics. The unexpected-exit diagnostic block is now suppressed on normaldocker stopshutdowns. — Thanks @Shokkstokk for the initial fix!profile_connectionscounter to go permanently negative, allowing connections beyond the configured profile limit. (1)_decrement_profile_connections()used a GET-before-DECR guard: two concurrent decrements could both read the same positive value, both pass the guard, and both fire, driving the counter below zero. Replaced with an unconditionalDECRfollowed by a clamp-to-zero if the result is negative. (2) Thestream_generatordecrementedactive_streamsand then checkedhas_active_streams()in two separate Redis round-trips without locking. A concurrent generator on another worker could readactive_streams=0in the window between those two calls and also decrement the profile counter, producing a double-decrement. A newdecrement_active_streams_and_check()method performs both operations under a single distributed lock, and aprofile_decrementedflag guards all four call sites in the generator so the profile counter is only ever decremented once per stream. (Closes #1125) — Thanks @firestaerter3stream_generator. When a stream ended via an unhandled exception path that reached thefinallyblock without any of the three exception handlers having run (e.g. an error raised before the firstyield), thefinallyblock decremented counters but never calledredis_connection.cleanup(). The upstreamrequests.Responseandrequests.Sessionwere left open until garbage collection. Thefinallyblock now starts adelayed_cleanupdaemon thread (matching the 1-second delay used by the normal-completion andGeneratorExitpaths) so that seeking clients have time to reconnect and incrementactive_streamsbeforecleanup()checks whether it is safe to close the connection.change_streamrequest it correctly packagedstream_idandm3u_profile_idinto the Redis pubsub message, but the owning worker's pubsub handler only consumedurlanduser_agentsilently dropping both IDs before callingstream_manager.update_url(). Becauseupdate_urlonly callsupdate_stream_profile()when astream_idis provided, theprofile_connectionscounter was never updated after the switch, causing subsequent capacity checks to see incorrect counts and bypass the full-profile guard. The handler now extractsstream_idandm3u_profile_idfrom the event and forwards them toupdate_url(). The bug did not affect single-worker / dev-mode deployments because the owning worker handles those requests directly without pubsub.next_streamrotation endpoint applying the same class of bug:get_stream_info_for_switch()was called and returnedm3u_profile_id, but the result was dropped when forwarding toChannelService.change_stream_url(), soupdate_stream_profile()was never called andprofile_connectionscounters were not updated after an automatic stream rotation.url,user_agent,stream_id,m3u_profile) being written to Redis before the switch was confirmed to succeed. If the switch failed, URL unchanged or exception during teardown, Redis described a URL not actually in use. Metadata is now written only afterupdate_url()returnsTrue; on failure the owner writesstream_manager.urlback as the ground truth. The non-owner no longer pre-writes metadata at all, all needed info is carried in the pubsub payload and written by the owner after confirmation.stream_id(the authoritative value already present in the stats payload) and re-runs only whenstream_idchanges, so the normal polling interval drives updates with no extra renders.PATCH /api/accounts/users/me/) stripping `Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
This PR has been generated by Renovate Bot.