Session title suggestions (#23) never appear: trigger is on a dead code path, and there is no surface for recurring sessions #278
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Reported from live use: no title suggestions offered for an upcoming recurring session. Investigation found two independent defects, either of which alone would produce the symptom.
Verified against prod (v3.11.0, containers built 2026-07-27, i.e. after #23 shipped 2026-07-17). LLM is configured (
app_settings.llm_configpresent and encrypted).Defect 1 — the generation trigger sits on a code path that is dead under the default retention mode
generate_session_title_suggestions.delay()is called from exactly one place:webapp/backend/app/routers/sessions.py:695, at the end ofapprove_audio.approve_audiorequiresaudio_processing_status == ready(sessions.py:607) andtransitions the session to
trashed.But
apply_post_processing_audio_retention(webapp/backend/app/tasks/reminder_tasks.py:2360)runs on the success path of
process_audio, and when the effective audio retention mode isdelete_after_processingit sets the session straight to the terminalapprovedstate(
reminder_tasks.py:2402), skippingreadyand the whole GM trash/grace flow. Its own docstringsays so explicitly.
delete_after_processingis the default (settings_service.py:82,_DEFAULT_AUDIO_RETENTION_MODE), and neither prod campaign sets an override(
campaigns.audio_retention_modeis NULL for both).Consequence:
approve_audiois never reachable, so the task never fires. Confirmed on prod —all 6 transcribed sessions are
audio_processing_status = 'approved'withaudio_trashed_atNULL (never trashed), and both campaigns have
next_session_title_suggestions_generated_at = NULL— the task has never run to completion foreither, despite two qualifying sessions completing since #23 shipped.
Fix direction: fire
generate_session_title_suggestions.delay(campaign_id)from the point thetranscript+summary are actually saved (end of
process_audio), not from the GM approval step —approval is an audio-retention concern, not a summary-availability one. Keep the
approve_audiocall as well for the non-default retention modes, or move it to a single shared helper so both
paths converge.
Defect 2 — no suggestion surface exists for a recurring session
TitleSuggestionsis rendered in exactly one place, the manual new-session form(
webapp/frontend/src/pages/CampaignDetail.jsx:2307), and it is explicitly suppressed when the"make recurring" toggle is on (
{!makeRecurring && <TitleSuggestions … />}, line 2306).Recurring occurrences are not created through that form at all — the
materialize_session_seriesBeat task inserts them with
title=series.title_template(reminder_tasks.py:4489). And the GMinline edit form on
SessionDetail.jsx(title/description, line 424) has no suggestion chips.Consequence: for a session that came from a series there is nowhere in the UI that offers a title,
even if suggestions were stored. The prod series (biweekly, Tuesdays 20:00, from 2026-08-11) has an
empty
title_template, so its materialized occurrences (2026-08-12 and 2026-08-26 UTC) have emptytitles with no way to fill them from suggestions.
Fix direction: render
TitleSuggestionsin theSessionDetailGM edit form — that is where aGM titles a session that already exists, which is the only path for a series occurrence. Worth
also reconsidering the
!makeRecurringsuppression: a title template arguably wants suggestionstoo, though that is a weaker case since the template is reused across occurrences.
Acceptance
retention mode, with no GM approval step.
SessionDetail.delete_after_processingpath firing the task.SessionDetailedit form.Workaround until fixed
Open + New Session (leave "recurring" unchecked) → ↻ Regenerate → wait → Refresh to get
chips, then copy a title into the recurring session's edit form. Nothing needs to be created.
Labels to apply: bug, backend, frontend
Audit follow-up — scope is wider than this issue as filed
A full sweep of LLM feature triggers (12 LLM features, 21 Celery enqueue sites, 8 in-request LLM call sites) changes the picture. Two additions and one correction.
Correction: the retention default is confirmed in force on prod, not hypothetical
app_settingson prod contains onlybot_api_key,bot_url,discord_bot_token,llm_config,lore_pipeline_mode,whisper_config— noaudio_retention_policyrow — and both campaigns haveaudio_retention_modeNULL. So_DEFAULT_AUDIO_RETENTION_MODE = delete_after_processingis what's running, and has been since recording started.Addition 1:
approve_audiogates four things, not twoBeyond the two in the issue body, the same dead endpoint also carries:
sessions.py:625-629) —extract_highlightsruns inline inprocess_audioand writes rows withapproved=False;approve_audiois the only thing that flips them toapproved=True, andlist_session_highlightsfilters non-GMs toapproved=True(sessions.py:729-730). The player-facing quote board (#116) is therefore permanently empty on a default install.session_summary_approvedbot event (sessions.py:665-679) — the bot posts a draft embed onsession_summarisedwith 👍⚠️❌ feedback reactions and only replaces it on approval. Prod: 5 of 6 transcribed sessions havesummary_discord_message_idset and none was ever replaced. Players have been reading uncorrected AI drafts with the feedback prompt still attached.sessions.py:694) — the only other trigger isbackfill_journal_entries, wired toworker_readyin a baretry/except: pass(reminder_tasks.py:4508-4515). Journal entries appear when a Celery worker next restarts, not after a session. (Moot on this instance —journal_entry_enabledis false on both campaigns.)Also worth noting the frontend gates transcript editing on
status === "ready"(SessionDetail.jsx:357), so a GM on the default mode can't correct a transcript either. Summary editing (SessionDetail.jsx:303) is ungated — which is why a GM can produce a corrected summary and then have no way to publish it.Addition 2: a third, independent defect in #23
Even with the trigger fixed, the task would mostly generate from nothing:
_generate_session_title_suggestions_asyncselects context fromSession.status == SessionStatus.completed(reminder_tasks.py:4287). Butprocess_audioandapprove_audioboth run while the session is stillconfirmed—update_campaign_storylinecompensates for exactly this with.in_([completed, confirmed])and the comment "Include confirmed — process_audio fires before auto_complete_sessions" (reminder_tasks.py:3970-3979). The title task never got that fix.suggestions = [](:4326) then unconditionally writesnext_session_title_suggestions = []with a freshgenerated_at(:4328-4331). A transient Ollama timeout blanks a previously-good set and stamps it as freshly generated.Revised fix direction
Moving one
.delay()is not the fix. Three separable pieces:sessions.audio_processing_statuscurrently encodes both "where is the WAV" and "has the GM signed off on the content" — note the enum comments (models/session.py:43-44):trashedmeans GM approved,approvedmeans audio permanently deleted. Content approval needs its own field, reachable in every retention mode.session_summary_available(session_id)fan-out, called fromprocess_audio(after commit), fromsession_service.update_sessionwhen"summary"is in the update, and from the canonical-name summary refresh (sessions.py:341). It enqueues lore proposals, journal, title suggestions, and the storyline rebuild. Collapses 3 mutually-unaware fan-out sites into 1 and fixes the mis-wiring as a side effect. Today only the storyline hears about manual summary edits.Admin UI copy needs updating too: the mode is labelled "Delete after processing (recommended)" (
Admin.jsx:769) and warns only "you won't be able to re-process the recording" (Admin.jsx:884). It should say the approval step, transcript editing, the Discord re-post, and highlight publication all go with it.Still open
Both prod campaigns have
highlights_max_quotes = 5andhighlights_in_discord = true, but zerosession_highlightsrows exist on any session — so extraction is producing nothing, separately from the publication gate.extract_highlightsnever raises (returns[]on error) and the delete-then-reinsert atreminder_tasks.py:2116-2120silently wipes prior rows. Needs its own investigation.Other mis-wirings the audit turned up are not part of this issue and should be filed separately:
generate_lore_entry_summaryhas one enqueue site (campaigns.py:3287) despite a docstring claiming entry-creation also triggers it (verified — entries from proposal approval, shelf promotion, and canonical-name auto-create never get a summary); andpropose_lore_relationshipsfires on proposal generation but readsproposed_by_bot == False, so it never sees the entries the session just produced.Fixed and deployed in v3.11.1 (PR #280, merged as
01c9f86).What shipped
Content approval is now its own state (
sessions.content_approved_at/content_approved_by_id) with its own endpoint,POST /sessions/{id}/approve, gated on having a summary rather than on audio state.audio_processing_statusgoes back to meaning only where the audio file is, and its enum comments no longer claim approval semantics.POST /sessions/{id}/audio/approveremains as a deprecated shim.The audio-trash transition survives as a conditional side effect, fired only when the audio is still
ready, so retain-mode installs keep their previous behaviour exactly.Also landed: the shared
summary_events.on_session_summary_available()fan-out replacing three mutually-unaware enqueue sites, and the two additional title-suggestion defects (thecompleted-only status window, and clobbering good suggestions on a transient LLM failure).Verified on the live deployment
Dev had no session with a summary, so the regression was seeded and driven through the real HTTP API:
approved) + summary — the regressioncontent_approved_atset, correct GM, audio state untouchedreadysessiontrashedThat last row is the one that matters for self-hosters: the conditional audio-trash still fires for retain-mode installs.
Prod deployed 2026-08-05 19:02 UTC. Migration
d9e0f1a2b3c4 → e0f1a2b3c4d5applied, all seven containers healthy, bot handshake clean (backend 3.11.1, bot API contract v1). The backfill correctly wrote nothing — no session on this instance hadaudio_trashed_at, because none had ever genuinely been approved. All eight transcribed sessions now show as approvable.Known gap
The Discord summary re-post could not be tested: the dev instance has no
DISCORD_BOT_TOKENand its campaign has no guild/channel. Thesession_summary_approvedpublish block was moved verbatim fromapprove_audio, with an unchanged payload and an unchanged bot handler (which already guards a nullsummary_discord_message_id), so what changed is reachability — and reachability is proven. Still, the first real approval on prod is the first live exercise of that path.Closing. The empty quote board is tracked separately in #279.
extractingforever #287