fix: resolve startup NameError in vector-sync metrics task

The Starlette lifespan started `vector_sync_metrics_task` with undefined
names `task_producer` and `receive_stream`. Those locals only exist inside
the `_wire_vector_sync_state` helper; in the lifespan the transport is bound
as `ingest_transport`. The undefined reference raised `NameError`, which
aborted the background-sync task group and crashed startup in every
deployment mode ("Application startup failed. Exiting.").

Introduced by fbe70ecd ("feat: backend-agnostic vector-sync gauges").

Pass `ingest_transport.producer` / `ingest_transport.receive_stream` at both
call sites (single-user app.py:1791, OAuth/login-flow app.py:2012).

Also fix 10 pre-existing `ty` possibly-missing-attribute diagnostics: the
deck indexing code in scanner.py, processor.py and search/context.py reads
full-DeckCard-only fields (description, type, owner, etag, lastModified) off
`stack.cards`, typed `list[DeckCard | DeckCardSummary]`. Freshly-fetched
stacks from `get_stacks()` always hold full DeckCards (the summary
projection only happens in the tool layer), so narrow with
`cast(list[DeckCard], ...)` — matching the existing pattern in
server/deck.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-04 22:10:40 +02:00
co-authored by Claude Opus 4.8
parent c47467f769
commit 1f8b3ba95e
4 changed files with 20 additions and 8 deletions
+5 -2
View File
@@ -19,6 +19,7 @@ from qdrant_client.models import FieldCondition, Filter, MatchValue, Record
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.client.news import NewsItemType
from nextcloud_mcp_server.config import get_settings
from nextcloud_mcp_server.models.deck import DeckCard
from nextcloud_mcp_server.observability.metrics import record_vector_sync_scan
from nextcloud_mcp_server.observability.tracing import trace_operation
from nextcloud_mcp_server.server.tag_exclusion import (
@@ -1097,8 +1098,10 @@ async def scan_deck_cards(
if not stack.cards:
continue
# Iterate through cards in stack
for card in stack.cards:
# Iterate through cards in stack. get_stacks() always yields full
# DeckCard objects; the DeckCardSummary projection only happens in
# the tool layer (server/deck.py), never on freshly-fetched stacks.
for card in cast(list[DeckCard], stack.cards):
# Skip archived cards
if card.archived:
continue