AppDisableListener enqueues invalid delete_provider_id (bare provider id) → blocks the whole action queue
Summary
AppDisableListener schedules a delete_provider_id action using the bare provider id (e.g. mail) instead of the full provider key appId__providerId (e.g. mail__mail). The backend rejects it because a provider id must match ^[a-zA-Z0-9_-]+__[a-zA-Z0-9_-]+$. Since the backend validates the entire action batch at once, this single invalid item makes the backend's action-processing thread throw on every poll, freezing the complete oc_context_chat_action_queue indefinitely (access-declaration updates, deletions, etc. stop propagating).
Affected code
lib/Listener/AppDisableListener.php (present unchanged on main):
foreach ($this->providerConfig->getProviders() as $key => $values) { // $key = "appId__providerId", e.g. "mail__mail"
$identifierValues = explode('__', $key, 2);
...
[$appId, $providerId] = $identifierValues; // $appId="mail", $providerId="mail"
if ($appId !== $event->getAppId()) {
continue;
}
$this->providerConfig->removeProvider($appId, $providerId);
$this->actionService->deleteProvider($providerId); // BUG: passes bare "mail"
}
ActionScheduler::deleteProvider() stores the argument verbatim as the payload:
public function deleteProvider(string $providerKey): void {
$payload = json_encode(['providerId' => $providerKey]); // -> {"providerId":"mail"}
...
}
Every other caller passes the full key. Compare the public API path, ContentManager::deleteProvider():
$this->actionService->deleteProvider(ProviderConfigService::getConfigKey($appId, $providerId)); // "mail__mail"
Root cause
AppDisableListener already holds the correct full key in $key, but passes only the split-off $providerId to ActionScheduler::deleteProvider(). The backend's provider-id validator (context_chat_backend/context_chat_backend/types.py, is_valid_provider_id) then rejects mail:
pydantic_core._pydantic_core.ValidationError: 1 validation error for ActionsQueueItems
actions.<id>.delete_provider_id.payload.providerId
Value error, providerId must be a valid provider ID string
input_value='mail'
Impact
Because context_chat_backend's updates_processing_thread validates the whole ActionsQueueItems batch in one model_validate() call, one invalid item raises and no actions in the batch are processed. The action queue stalls completely and never recovers on its own; a stale row keeps poisoning every poll. Observed effect: 500+ update_access_decl_source_id actions stuck for ~1 day; ACL/share changes not reflected in semantic search results until the poison rows were removed manually.
Trigger
AppDisableEvent — fired whenever an app that registered a context_chat content provider is disabled, including during app updates / occ upgrade. The Nextcloud Mail app registers a content provider with id mail, so each disable produces one poisoned delete_provider_id: mail row.
Suggested fix (context_chat)
Pass the full provider key:
- $this->providerConfig->removeProvider($appId, $providerId);
- $this->actionService->deleteProvider($providerId);
+ $this->providerConfig->removeProvider($appId, $providerId);
+ $this->actionService->deleteProvider($key);
($key is already appId__providerId; equivalently ProviderConfigService::getConfigKey($appId, $providerId).)
Secondary hardening (context_chat_backend)
updates_processing_thread should validate/skip actions per item (as the files path already does via ItemValidationError) rather than aborting the entire batch, so a single malformed action can never freeze the whole queue.
Environment
- context_chat (PHP app): 5.4.0
- context_chat_backend: 5.4.1
- Nextcloud: AIO
- Bug present on
nextcloud/context_chat@main (lib/Listener/AppDisableListener.php, line ~51), unchanged; no existing issue/PR found.
AppDisableListener enqueues invalid
delete_provider_id(bare provider id) → blocks the whole action queueSummary
AppDisableListenerschedules adelete_provider_idaction using the bare provider id (e.g.mail) instead of the full provider keyappId__providerId(e.g.mail__mail). The backend rejects it because a provider id must match^[a-zA-Z0-9_-]+__[a-zA-Z0-9_-]+$. Since the backend validates the entire action batch at once, this single invalid item makes the backend's action-processing thread throw on every poll, freezing the completeoc_context_chat_action_queueindefinitely (access-declaration updates, deletions, etc. stop propagating).Affected code
lib/Listener/AppDisableListener.php(present unchanged onmain):ActionScheduler::deleteProvider()stores the argument verbatim as the payload:Every other caller passes the full key. Compare the public API path,
ContentManager::deleteProvider():Root cause
AppDisableListeneralready holds the correct full key in$key, but passes only the split-off$providerIdtoActionScheduler::deleteProvider(). The backend's provider-id validator (context_chat_backend/context_chat_backend/types.py,is_valid_provider_id) then rejectsmail:Impact
Because
context_chat_backend'supdates_processing_threadvalidates the wholeActionsQueueItemsbatch in onemodel_validate()call, one invalid item raises and no actions in the batch are processed. The action queue stalls completely and never recovers on its own; a stale row keeps poisoning every poll. Observed effect: 500+update_access_decl_source_idactions stuck for ~1 day; ACL/share changes not reflected in semantic search results until the poison rows were removed manually.Trigger
AppDisableEvent— fired whenever an app that registered a context_chat content provider is disabled, including during app updates /occ upgrade. The Nextcloud Mail app registers a content provider with idmail, so each disable produces one poisoneddelete_provider_id: mailrow.Suggested fix (context_chat)
Pass the full provider key:
(
$keyis alreadyappId__providerId; equivalentlyProviderConfigService::getConfigKey($appId, $providerId).)Secondary hardening (context_chat_backend)
updates_processing_threadshould validate/skip actions per item (as the files path already does viaItemValidationError) rather than aborting the entire batch, so a single malformed action can never freeze the whole queue.Environment
nextcloud/context_chat@main(lib/Listener/AppDisableListener.php, line ~51), unchanged; no existing issue/PR found.