Session title suggestions (#23) never appear: trigger is on a dead code path, and there is no surface for recurring sessions #278
Closed
opened 2026-08-05 15:30:47 +00:00 by claude-bot
·
2 comments
No Branch/Tag specified
main
feat/1328-fill-it-in
feat/1328-fill-frontend
feat/1328-fill-backend
feat/9-foundry-push
fix/9-foundry-live
feat/9-foundry-push-frontend
renovate/celery-5.x
feat/5-foundry-connect
feat/9-foundry-push-backend
feat/9-foundry-push-module
feat/5-foundry-frontend
feat/5-foundry-backend
feat/5-foundry-module
release/v4.8.0-rc1
docs/d341-convert-draft
feat/958-update-to-version
feat/958-convert-sheet
feat/958-pathbuilder-import-frontend
fix/1360-heading-order
feat/958-pathbuilder-import-backend
feat/11-weekly-digest
feat/1341-auto-update
docs/d332-superseded
chore/1345-names-cleanup
feat/1342-mentioned-pages
feat/1328-recipes
fix/1346-pgdg-key
perf/1247-shared-recording-stream
chore/132-gitleaks-allowlist
release/v4.7.1
spike/foundry-module
release/v4.7.1-rc1
docs/decisions-d332-d337
fix/bot-certifi
chore/release-4.7.0
chore/release-4.7.0-rc4
chore/deps-4.7.0
chore/release-4.7.0-rc3
fix/1280-quote-must-say-it
fix/1257-names-row-squeeze
chore/release-4.7.0-rc2
fix/1245-pg-upgrade-count
chore/release-4.7.0-rc1
feat/1257-names-in-prep
feat/1257-be
feat/1257-fe
feat/1257-rating-fixes
design/1328-fill-it-in
feat/1257-name-splits
fix/1326-redis-503
feat/1280-fact-suggestions
feat/1280-be
feat/1280-fe
design/1280-fact-suggestions
feat/1279-infobox
feat/1279-be
feat/1279-fe
feat/1321-shelf-copies
design/1279-infobox
feat/1243-table-find
feat/1257-who-rated
feat/1243-prep-drawer
feat/1243-sheet-pickers
design/1243-browse-in-place
feat/1315-hotwords-default
feat/1242-system-filters
feat/1257-more-name-styles
feat/506-leg3-hotwords
design/1242-system-filters
fix/1302-packs-polish
feat/1241-rules-library
design/1241-rules-library
feat/1245-postgres-18
integ/4.6.2
fix/1263-map-ui-2
fix/1263-map-server
fix/1263-map-ui-1
fix/1261-editor-link-menu
fix/1229-axe-4.14-name-mismatch
renovate/axllent-mailpit-1.x
renovate/eslint-monorepo
renovate/authlib-1.x
chore/release-4.6.0
chore/release-4.6.0-rc2
fix/460-rc2-screens
fix/460-rc2-server
fix/release-prerelease-exit
chore/release-4.6.0-rc1
feat/125-campaign-templates
feat/126-calendar
feat/1181-pictures-and-files
feat/1190-event-people
integ/4.6.0
feat/1191-line-of-the-night
feat/1200-starter-packs
chore/bot-multidict-and-playwright-group
fix/1202-this-week-answered-count
fix/122-124-leftovers
fix/1178-get-routes-that-write
fix/1203-journeys-check-names
design/125-campaign-templates
feat/126-calendar-engine
design/126-in-world-calendar
fix/1189-cited-stamp-in-range
fix/1189-v4-looser-check
fix/1189-richer-beats
fix/1113-export-bind-lists
fix/1154-release-notes-by-file
chore/release-4.5.0
chore/release-4.5.0-rc12
fix/1135-wiki-markup-as-text
fix/demo-usage-cost-in-window
chore/release-4.5.0-rc11
fix/1121-sheet-consistency
fix/1121-sheet-5e-terms-and-card-hp
copy/1122-busiest-pages
fix/1121-relevant-choices
fix/1121-wiki-create-npc-save-sheet-links
fix/1029-menu-stays-on-screen
docs/prototype-transcript-bin-wording
fix/1023-editor-save-row-back-scroll
fix/1020-search-exact-first
fix/1020-table-prep
fix/1020-shell-wordmark-footer-bell
fix/1020-wiki-article-party
fix/872-admin-kept-until-phone-tables
feat/l13-milestone-level-waiting
fix/1000-1001-bell-older-setup-context
fix/999-pronouns-in-prompts
fix/l19-other-class-feats
fix/1010-transcript-window-ordinary-confirm
docs/d254-d269-decisions
fix/702-member-search
fix/680-transcript-bin
fix/993-unidentified-prompts
fix/841-redis-timeouts
fix/990-binned-not-exported
fix/830-export-prep-shelf
fix/deps-audit-2026-10-01
fix/877-title-reads
fix/798-audio-received-at
build/lane11-17-j7
build/lane11-16-levelup
build/lane11-14-seams
build/lane11-14-seed
build/lane11-15-start
build/lane11-13-old-block-out
fix/980-review-r3
build/lane11-12-block
build/lane11-8-packs
fix/demo-seed-canonical-names
build/lane11-11-hot-half
docs/l11-decisions
build/lane11-7-convert
build/lane11-10-editing
fix/npm-audit-brace-expansion
build/lane11-9-sheet-page
fix/bot-pyjwt
build/lane11-6-content
fix/lane11-rank-names
build/lane11-2b-language
build/lane11-4-js-evaluator
build/lane11-3-server
build/lane11-5a-pf2e
build/lane11-5b-5e
feat/design-admin
build/lane11-2-evaluator
build/lane11-1-sheet-pieces
build/lane11-0-drawing
fix/943-quest-objectives-fk
fix/prepsheet-shelf-flake
fix/945-leftovers
build/demo-gm-copy
fix/901-email-landings
build/lane10-8-journeys
build/demo-seed-lanes-8-10
fix/887-gm-own-confirmation
build/lane9-5-j1-admin
fix/918-audience-click
fix/useasync-overtaken
ci/939-critical-path
test/937-backend-suite-speed
chore/claude-orchestration
fix/924-xp-gm-only
fix/session-stale-poll
docs/decisions-d239-d242
fix/930-pypdf
build/lane8-journeys
build/925-quest-editor
fix/894-private-page-pictures
build/lane8-p4-drafts
build/lane8-p7-map
build/lane8-p6-handouts-rules
fix/wiki-fixture-names
build/lane10-d227-test-usage
fix/rsvp-horizon-test-tuesday
fix/media-volume-owner
build/lane8-p3-editor
fix/912-unidentified-rename
build/lane8-p5-party-quests
build/lane8-handout-takeback
build/lane10-pickers
build/lane8-p2-article
build/lane8-6-map
build/lane10-7b-email-pages
build/lane8-5-handouts
build/lane10-7a-test-the-bot-pages
build/lane8-p1-pages
build/lane8-4-quests
build/lane8-2b-feeds
build/lane8-3-parties
build/lane8-2a-links
build/lane10-5-commands
build/lane9-6-seed
build/lane10-4b-reactions
build/lane9-4d-pages
build/lane9-4c-pages
build/lane10-4a-posts
build/lane10-3-email
fix/891-web-fonts
build/lane9-4b-pages
build/lane10-6-test-the-bot
build/lane10-2-notify-out
build/lane8-1-wiki-text
build/lane9-4a-frame
design/lane10-drawings
fix/j6-budget
fix/j5-phone-flake
fix/869-test-leaks
fix/843-record-start-followups
build/lane7-all
build/lane6-all
build/lane9-b2-server
chore/changelog-fragments
docs/decisions-d218
build/lane9-a-owner-overview
fix/rule-card-level-label
fix/834-retention-semantics
fix/o3c-record-without-announcement
docs/decisions-d179
docs/decisions-d131-d170
fix/851-commit-before-response
build/lane6-j3-seed
build/lane9-b1-d67-identity
fix/841-blocking-loop
build/lane9-c-backups-audit
fix/vote-race-and-start-commit
fix/recording-announcement
fix/proposal-read-leak
fix/last-admin-guard
build/lane6-p4c-planning
build/lane6-p4b-margin
build/lane6-p4a-sheet
build/lane5-j4-seed
build/lane6-base
build/lane7-pages-talk
build/lane5-pages
build/lane7-pages-safety
build/lane7-p3-safety
build/lane7-p2-notify
build/lane6-p3-planning
build/lane6-p2-history
build/lane6-p1b-items
build/lane7-p1-threads
build/lane5-p6-faces
build/lane6-p1a-sheet
build/lane5-p5-find
build/lane5-p4a-board
build/lane5-p4b-scenes
build/lane5-p3-live
build/lane5-base
build/lane5-p2-cards
docs/lane5-decisions
build/review-r2-wide
fix/review-r1
build/lane5-p1-scenes
build/demo-seed
build/lane4-journeys-j5
build/lane4-page-review-b
build/lane4-page-review-a
build/lane4-page-read
build/lane4-journeys
build/lane4-page-sess2
build/lane4-page-sess1
build/lane4-notes
build/lane4-discord-on-approval
build/lane4-scheduling
build/lane4-versions
build/760-session-number
build/lane4-decisions
build/797-erase-no-regenerate
build/test-race-audit
build/lane3-completions
build/794-dark-only
build/796-titles-scroll
build/791-retention-anchor
build/a11y-sweep
build/shell-followups
build/retention-preview
build/753-backup-readback
docs/d91-d93
build/lane3-j1
build/ci-harness-paths-selftest
fix/useauth-test-race
build/ci-harness-paths
build/777-ai-settings-read
build/lane3-settings-c
build/lane3-settings-b
build/lane3-j6
build/lane3-sessionform
build/lane3-settings-a
build/lane3-members
build/lane3-thisweek
build/lane3-story
chore/87-align-python-3.12
v4.8.0-rc1
v4.7.1
v4.7.1-rc1
v4.7.0
v4.7.0-rc4
v4.7.0-rc3
v4.7.0-rc2
v4.7.0-rc1
v4.6.2
v4.6.2-rc4
v4.6.2-rc3
v4.6.2-rc2
v4.6.2-rc1
v4.6.1
v4.6.1-rc1
v4.6.0
v4.6.0-rc2
v4.6.0-rc1
v4.5.3
v4.5.3-rc1
v4.5.2
v4.5.2-rc1
v4.5.1
v4.5.1-rc2
v4.5.1-rc1
v4.5.0
v4.5.0-rc12
v4.5.0-rc11
v4.5.0-rc10
v4.5.0-rc9
v4.5.0-rc8
v4.5.0-rc7
v4.5.0-rc6
v4.5.0-rc5
v4.5.0-rc4
v4.5.0-rc3
v4.5.0-rc2
v4.5.0-rc1
v4.4.1
v4.4.0
v4.3.0
v4.2.3
v4.2.2
v4.2.1
v4.2.0
v4.1.2
v4.1.1
v4.1.0
v4.0.1
v4.0.0
v3.11.4
v3.11.3
v3.11.2
v3.11.1
v3.11.0
v3.10.0
v3.9.1
v3.9.0
v3.8.0
v3.7.0
v3.6.1
v3.6.0
v3.5.0
v3.4.1
v3.4.0
v3.3.0
v3.2.0
v3.1.0
v3.0.0
v2.1.0
v2.0.0
v1.1.0
v1.0.0
v0.12.0
v0.11.0
v0.10.0
v0.9.1
v0.3.3
v0.9.0
v0.8.0
v0.3.2
v0.3.1
v0.3.0
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.2.1
v0.2.0
v0.1.1
v0.1.0
Labels
Clear labels
backend
Issue related to the backend
bot
Issue related to the bot
bug
Something is not working
duplicate
This issue or pull request already exists
enhancement
New feature
frontend
Issue related to the frontend
help wanted
Need some help
invalid
Something is wrong
question
More information is needed
recording
Issue related to the recording system
security
Security hardening / vulnerability work
wontfix
This won't be fixed
No labels
backend
bot
bug
duplicate
enhancement
frontend
help wanted
invalid
question
recording
security
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Quest-Board#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