Merged
Conversation
The branded PDF read loud (heavy saturated-blue masthead + all-blue headings). Restyle toward a restrained, professional look while keeping the same content, Unicode fonts, and full text-parsability: - Mostly ink-on-white with generous whitespace; cobalt is now a sparing ACCENT only -- a hairline under the header, small tracked uppercase section labels, speaker names, and list markers. - Masthead: logo on white (top-left) with a tracked "MEETING MINUTES" eyebrow and a cobalt signature rule, instead of a filled blue band. The LLM-generated title is the dominant element (left-aligned, ink). - Transcript table lightened: muted uppercase column labels, subtle zebra rows, cobalt speaker names, muted mono times (widened so timestamps stay on one line), top-aligned rows -- no database-style blue header block. - Left-aligned body/title (no justification word-gaps). pdf.py only; render_meeting_pdf / _document_title public API unchanged, so test_pdf.py is untouched and still passes. Verified visually across accented / Cyrillic / Greek content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
style(meeting): sleeker, more editorial minutes PDF
…output Four related changes to the meeting recorder, all in the transcription path. 1. Decoder emitted ~20% padding into every PCM stream `bytes(frame.planes[0])` returns the whole FFmpeg-aligned plane allocation, not the samples: 768 bytes for a 320-sample (20ms) frame. That spliced ~6ms of padding into the audio after every frame -- corrupting what AWS Transcribe received, and inflating the byte-derived timebase by 19.8%. Slice to `samples * 2`. Measured: 1020ms of audio decoded to 1222ms before, 1019ms after. `test_audio.py`'s `0.5x..2x` tolerance is what hid this; it is now `0.9x..1.0x`, plus a duration test and a per-chunk padding test. 2. Speaker timestamps drifted after the first utterance A speaker's buffer holds only the frames they spoke -- silence is never buffered -- so Transcribe's buffer-relative word times are compressed speaking-time. The old single `base_ts_ms` anchored only the first word, so cross-speaker ordering went wrong and the 3s-gap segmentation rule never fired (the buffer has no gaps), collapsing each speaker into one giant segment. Replaced with `(buffer_offset_ms, meeting_ts_ms)` anchors recorded whenever a frame arrives later than the buffered audio accounts for. Buffer position is tracked in BYTES and converted once, so per-chunk rounding cannot accumulate into a false gap. 3. Persistent per-speaker Transcribe streams `transcript_view()` used to re-transcribe each speaker's WHOLE buffer on every poll, and again at stop -- so k polls re-sent ~k/2 copies of the audio and AWS cost grew with the SQUARE of meeting length, with each poll getting slower. Now each speaker gets one stream held open for the meeting: audio is pushed in as it arrives and never replayed, `transcript_view()` is a free read of what has finalized, and `stop()` closes the streams. `send()` is synchronous and thread-safe (the WS ingest worker thread has no running loop); the session captures the loop, since it is constructed on it. AWS ends sessions on its own (idle timeout, 4h cap), so the wrapper reopens transparently and offsets each new session's word times by the audio already delivered. `aclose()` bounds its wait for AWS's final flush at 15s so a misbehaving stream cannot hang meeting finalize. Side effect: nothing retains PCM any more, because nothing replays it. Per-speaker memory drops from O(meeting duration) to O(words). 4. Audio output and all disk writes removed `audio_b64` is gone from `StopResponse` and the bot no longer attaches an MP3. That made the mixer and the per-speaker PCM files unreachable, so `audio/mixer.py`, `run_ffmpeg`, `opus_to_pcm16k_args`, `MixerAdapter`, the `tmp_root`/`mixer` deps and the per-session temp dir are all deleted -- as is the `ffmpeg` apt layer in the Dockerfile (PyAV bundles its own, and nothing shells out). The service now touches the filesystem zero times. The path-traversal regression test is replaced by one that patches `builtins.open`/`io.open`/`os.open`/`os.mkdir`/`os.makedirs`/`tempfile.*` and proves a full create -> feed -> stop cycle opens nothing. Patching `builtins.open` alone was verified insufficient (`io.open` and `pathlib.Path.open` slip past it). Also: the bot @-mentions whoever ran `/record start` when posting the minutes. The id is stored on the session rather than read off the stopping interaction, so auto-stop -- which has no interaction -- pings them too. And `feed()` is refused once `stop()` has begun, so a frame arriving mid-finalize is rejected outright instead of being appended to an already-transcribed buffer and lost. Docs for these changes are NOT included here; they are entangled with the in-flight platform docs refresh (PR #133) in the same files. Verified: 89 passed in services/meeting (was 77), ruff clean, discord-bot at its usual 31/33 (the 2 failures are a local Node 18 artifact -- CI and the image are Node 20). Regression tests were each confirmed to fail before the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of the persistent-stream work surfaced two reproducible lifecycle bugs and several quality issues. Each fix has a regression test that was watched fail first. Critical -- a session ending alongside new audio killed the speaker `asyncio.wait(FIRST_COMPLETED)` can return with BOTH the queue read and the output-stream consumer ready. The queue was checked first, so audio was sent into a session AWS had already closed; that raised, tripped the failure handler, and left the speaker a black hole for the rest of the meeting -- audio drained and discarded, one log line the only signal. AWS ends sessions on idle timeout, at the 4h cap, and on transient errors, so this was ordinary, not exotic. Reproduced: 1 session opened, later chunks never delivered. Now the AWS side is checked first and anything pulled off the queue is carried into the next session. After: 2 sessions, all audio delivered. Critical -- one transient AWS error disabled a speaker permanently The failure handler was terminal. It now retries, giving up only after `_MAX_SESSION_FAILURES` consecutive failures, with `_MAX_BARREN_SESSIONS` guarding against a hot reopen loop when sessions end without accepting audio. Important -- abort() before the pump spawned leaked a task `start()` schedules the pump via `call_soon_threadsafe`; an `abort()` landing in that window set `_closing` but `_task` was still None, so nothing was cancelled and the pump then parked forever on a queue nothing would feed. Reachable on the abrupt-disconnect path. Fixed with a distinct `_aborted` flag checked by `_spawn` -- deliberately separate from `_closing`, since `aclose()` still needs the pump to run to flush finals. Important -- stop() finalized speakers serially Each `finalize()` can wait up to FINAL_FLUSH_TIMEOUT_S for AWS. Serial made worst-case /stop latency N x 15s; a 10-speaker meeting would block ~150s, past the bot's HTTP timeout, losing the minutes. Now `asyncio.gather` with `return_exceptions=True`, so one speaker's failure cannot lose everyone else's transcript. Important -- the pending-replay path was unreachable, and its test proved nothing The replay test stubbed `_run_session` to manufacture a state the real one could not produce. The Critical fix above makes that path genuinely reachable, so the test is rewritten to drive it through the public API only. Important -- the fake hid what aclose() exists for `_LiveStream.end_stream()` closed the output stream synchronously and emitted nothing, so aclose()'s central claim (closing turns trailing partials into finals) was asserted nowhere and the 15s timeout branch had no coverage. Added `_FlushingStream` (emits a final AFTER end_stream) and a never-closing stream, plus tests for both. The fake input stream now also rejects sends after close, like the real service -- which is what let the Critical bug hide. Important -- drain() was test-only code on the production class Removed. The runtime contract is now start/send/words/aclose/abort; the synchronisation helper lives in the test module, where poking private state is legitimate. An event-driven version was tried first and rejected: it made the suite 30s instead of 0.25s. Minor: deleted a dead `asyncio.Event`; made the per-session byte offset a local; derived bytes-per-ms from the configured sample rate instead of hardcoding 32 (a non-16kHz stream would silently mis-offset restarts); fixed a docstring naming a renamed attribute; refreshed three lock comments and a `discard()` docstring that still described `pcm_chunks` and buffered audio; removed a stale `feed()` comment describing the re-transcription problem this branch fixes. Verified: 96 passed in services/meeting (was 91), ruff clean, discord-bot 31/33 (the 2 are the known local Node 18 artifacts). Both lifecycle bugs re-probed against the fixed code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The code changes here make several documented claims wrong: the `/stop` response shape, the ffmpeg dependency, and the re-transcription cost model. Written against the staging baseline rather than lifted from the in-flight platform docs refresh (#133), so this branch is self-consistent on its own and that work stays independent. Expect a small conflict in these three files when #133 lands. - `POST /stop` no longer returns `audio_b64`; documented as PDF-only, with the `title` field the response actually carries. - No `ffmpeg` binary anywhere: not a local prerequisite, not in the image, not invoked per frame. Opus decode is in-process via PyAV. Several of these were already stale before this branch (they described the pre-PyAV decode path), but deleting `run_ffmpeg` makes them definitively wrong. - Transcription described as persistent per-speaker streams, with `/transcript` as a free read, replacing the "re-transcribes the whole buffer on every poll" model throughout. - Nothing is written to disk; `discard()` aborts streams rather than deleting a temp dir; the session holds streams, not buffered PCM. - The bot posts the PDF alone and @-mentions the requester. - "Known limitations" replaced with what is actually unverified now: concurrent Transcribe stream quota, AWS session restarts, and the 200ms anchor tolerance being reasoned rather than measured. - Dropped the "Not implemented: true incremental streaming transcription" bullet, which is exactly what this branch adds. - Dropped a hardcoded, long-wrong test count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
From CodeRabbit review on #136; all four verified against the code first. - `services/meeting/README.md`: `session_id`'s regex was justified as "used to build a filesystem path for temp audio". There are no filesystem writes left, so that justification re-suggests a traversal surface this branch removed. Kept the regex, corrected the reason. - `services/meeting/README.md`: `GET /transcript` still described as re-running transcription over buffered audio so "polls can refine earlier text". That is the model this branch replaces; polling is now a free read. - `docs/MEETING-RECORDING.md`: the "Timeline correctness" section still described anchoring on each speaker's FIRST `ts_ms` -- the buggy behaviour this branch fixes. A reader following it would reimplement the bug. - `docs/MEETING-RECORDING.md`: dropped a "Concurrency" bullet duplicating the new concurrent-stream-limits one. - `discord-bot/README.md`: "No audio or transcript is persisted" was too broad. The posted PDF contains the full transcript and lives in Discord; the claim now scopes to the service not writing audio or transcript files. - `src/stt/transcribe.py`: `send()` counted bytes before confirming the enqueue was scheduled, so a failed `call_soon_threadsafe` would skew `_sent_bytes` against `_delivered_bytes` permanently. Moved into the success path. Verified: 96 passed, ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(meeting): persistent per-speaker Transcribe streams; drop audio output
…CI (#134) The platform-level docs hadn't been updated since 2026-07-14 and still described a three-service platform. It is five services plus the bot: llm, verification, and meeting were missing from every service list, env-var table, deploy order, and key-provisioning step. Separately, documentation-system/docs/ never documented the doc visibility + grants work: two endpoints, a table, and an entire second authorization layer were absent. Platform docs - DEVELOPMENT.md: all five services with ports/DBs, quick starts for llm/verification/meeting, updated topology, key minting for the DB-free services, expanded troubleshooting. - RAILWAY-DEPLOYMENT.md: per-service Railway table, three Neon projects (not two), split env-var tables, deploy ordering, and a new step 4b for the manual llm/meeting CONSUMER_KEYS provisioning that provision-directory-key.sh does not cover. - DEPLOYMENT-HISTORY.md: corrected topology, the two no-DB services and the one stateful service as explicit design choices, 9-job CI list, a 2026-07-26 meeting-recording release entry, and a known-gaps section. - ARCHITECTURE.md: visibility resolution as the second cross-service leg, three auth storage models, row-level visibility convention. documentation-system - API.md: grants endpoints, DocGrant shape, the X-On-Behalf-Of actor model, docs:read:all, and why an identity-less docs:read key sees nothing. Documents 404-not-403 for invisible docs. - ARCHITECTURE.md: doc_grants table incl. the partial index that catches duplicate org grants (NULL != NULL), the visibility rule and its dual implementation, and the SSRF egress guard. - DEPLOYMENT.md: four migrations, with 004 flagged as a data migration. Corrections to claims that were wrong - team-tracking has 26 endpoints across 7 routers, not 23 across 6. - verification uses a NullApiKeyStore: no per-consumer keys exist, only the bootstrap API_KEY. Docs implied a CLI-minted model. - meeting was documented on port 8003, which verification already uses; its docs now say 8004. Nothing in code binds it, so this is docs-only. - discord-bot/README listed /record status and /record stop as (linked); both are auth: 'public' in commands/record.js. - Removed a link to a design archive deleted in a5e1bd1. Defects documented, not fixed - documentation-system and verification both bind host port 5434 for dev Postgres and cannot run locally at the same time. CI - Add meeting-test (96 tests that ran nowhere) and a meeting image build + smoke step to docker-build. ruff format --check is deliberately omitted for meeting: 8 files are unformatted at HEAD and the step would land the job red. Reason is commented in the workflow. Verified: meeting suite passes on staging (96 tests), ruff check clean, the meeting image builds and imports, ci.yml parses (9 jobs), and all internal links across 22 markdown files resolve. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Investigating a transcript that cuts off at the end.
`send()` hands audio to the loop with `call_soon_threadsafe`, so the enqueue is
DEFERRED to the next loop iteration. `aclose()` ran on the loop and called
`self._queue.put_nowait(None)` DIRECTLY, which executes immediately -- so the
close sentinel was enqueued ahead of audio that `send()` had already accepted
but not yet queued. The pump saw the sentinel, ended the AWS stream, and exited;
the audio callbacks then fired into a queue nobody was reading.
That is the shape of the end of every meeting: the WS ingest worker thread
delivers the last frames and `POST /stop` arrives right behind them. Reproduced
deterministically -- two chunks handed to send() from a worker thread
immediately before aclose() never reached AWS:
delivered to AWS: [b'early'] LAST1: False LAST2: False
The sentinel now goes through the same `call_soon_threadsafe` path, so it is
ordered behind everything already scheduled, and aclose() awaits the enqueue
before waiting on the pump. After:
delivered to AWS: [b'early', b'LAST1', b'LAST2']
Verified: 97 passed (was 96), ruff clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review follow-ups on #137. Two are real bugs; the second was hiding behind a test that passed for the wrong reason. 1. aclose() could hang forever if the sentinel enqueue raised `flushed.set_result` sat on the success path, so an exception inside the callback was swallowed by the loop and the future never resolved. That is the un-timeouted /stop finalize path, so the whole meeting wedges. The callback now reports success/failure through the future in a `finally`, and if the sentinel could not be enqueued the pump is aborted -- waiting on a pump that can never be told to stop would hang just as badly. 2. aclose() swallowed CancelledError, so it could not be timed out `except asyncio.CancelledError: pass` was meant for an abort() racing the close, but it also swallowed cancellation of aclose() ITSELF. A caller wrapping it in wait_for got a silent success instead of a timeout. This is how the new regression test passed while still taking the full 5s: wait_for cancelled it, the handler ate the cancellation, and the coroutine "returned". Now only the pump's own cancellation is swallowed (`_aborted` and `_task.cancelled()`); anything else re-raises. The test drops from 5.00s to 0.00s, which is what made the problem visible. Also: state the ordering guarantee accurately. It covers audio whose send() has RETURNED, not audio "handed to send()" -- a call past the _closing check but not yet at call_soon_threadsafe can still lose its frame. That residue is at most one in-flight feed(), since MeetingSession._stopping stops accepting frames when /stop begins; both facts are now in the comment. Dropped frames in the closing window also log at debug, so the next "transcript cut off" report is diagnosable. Removed the dead non-loop fallback branch, which was the exact bug being fixed left standing. Joined the worker thread in the test. Verified: 98 passed (was 97), ruff clean, and the original end-of-meeting repro still delivers every chunk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… claims CodeRabbit nitpicks on #137, both valid. - aclose() had future creation, a closure, call_soon_threadsafe and an await inline. Extracted to _signal_end_of_audio(), which is also where the ordering rationale now lives -- it is the reason the method exists. - test_aclose_returns_even_if_the_sentinel_enqueue_raises claimed in its docstring that words finalized before the break survive, but only asserted that aclose() returned. It now pushes a final result first and asserts it comes back. 98 passed, ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(meeting): stop the close sentinel from overtaking the last audio
Second cause of "the minutes cut off", separate from the audio-tail bug in #137 and unrelated to transcription. `summarize_minutes` capped the model at max_tokens=1500. A busy meeting's JSON runs longer, so it was truncated mid-write; `_extract_json` then failed outright (no closing brace) and the fallback put the RAW TRUNCATED JSON into the summary field. A reader saw a wall of JSON ending mid-word, with every decision and action item gone -- even though the model had produced them: summary : '{"title": "Weekly Sync", "summary": "The team discussed the roadmap and hiring plans for next quarter.", "deci' ... decisions : [] action_items: [] Three changes: - `_salvage_truncated` parses a cut-off object by walking it once, recording positions where the document was structurally complete, then rewinding and closing what is still open. Clean value boundaries are preferred so nothing half-written is recovered; closing an open string is used only when no clean boundary yields a summary (the truncation landed in the summary itself, where a partial sentence beats losing it). Same input now yields the title, the full summary, both decisions, and the one completed action item. - max_tokens 1500 -> 4000 (MINUTES_MAX_TOKENS). The llm service permits 16000, so 1500 was a self-imposed limit far under what was available, which made truncation routine rather than exceptional. Salvage is the safety net; this makes it rarely needed. - An unparseable, unsalvageable fragment starting with '{' or '[' becomes an explicit placeholder and logs a warning, instead of being shown to a human as if it were prose. Genuine prose from a model that ignored the JSON instruction is still used as the summary, as before. Verified: 101 passed (was 97), ruff clean. Five new tests, each watched fail first, including one pinning the prose fallback against regression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review follow-ups on #138. 1. The "never surface raw JSON" guarantee leaked on the two commonest wrappers The guard tested `content.startswith("{")`, but a truncated response never closes its fence, so ```json blocks and "Here are the minutes:" lead-ins sailed past it and the raw fragment reached the reader anyway -- the exact wall of JSON in the PDF the guard exists to prevent: '```json\n{"title":"T","summar' -> summary = '```json\n{"title":"T","summar' Now checks the unwrapped candidate plus a narrow `[{[]\s*"` shape signal, so a fragment is caught wherever it sits. Ordinary prose (no `{"`) is still used as the summary, pinned by an existing test. 2. Salvage was quadratic on an unparseable response Every checkpoint costs a json.loads over a growing prefix, and a fragment with no top-level summary walks all of them. Measured on nested objects: len= 10013 204.7 ms -> 7.1 ms len= 40013 2891.3 ms -> 34.4 ms len= 80013 (n/a) -> 75.3 ms Capped at the newest _MAX_SALVAGE_ATTEMPTS (64) checkpoints. A usable recovery point is essentially always within a few of the truncation, and the response is untrusted input from a network service. 3. Lossy salvage was completely silent Recovering a truncated response DROPS whatever came after the cut, so the PDF can look authoritative while a decision the model actually made is missing. The failure path logged; the lossy-success path did not. It does now. Minor: `best = best or parsed` treated an empty dict as "no candidate" -- now `if best is None`. Dropped a dead `.rstrip().rstrip(",")` (a checkpoint always lands right after a quote or bracket). Softened the docstring claim that nothing half-written is recovered: a clean boundary can land inside a NESTED object and keep a partial one -- harmless under the string[] schema this prompt asks for, but not the guarantee the wording implied. Verified: 104 passed (was 101), ruff clean. Three new tests, each watched fail first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CodeRabbit on #138, confirmed and a regression this PR introduced. Salvage can return a dict with real content but no `summary` -- the model was cut off before that value closed. summarize_minutes still required a string summary and discarded everything otherwise, so a title and decisions the model genuinely produced were thrown away in favour of the generic placeholder: salvaged dict : {'title': 'Q3 Roadmap Sync', 'decisions': ['Ship Friday']} title kept : '' decisions kept: [] Before salvage existed this could not happen -- _extract_json returned either None or a fully valid object, so requiring `summary` was safe. It isn't any more. The parsed-but-summaryless case now keeps the other fields and says the summary was cut off, and logs. Only a genuinely unparseable response takes the prose/placeholder path. 105 passed, ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex review on #138. Three findings, all verified before acting; two were regressions introduced by the previous round's fixes. 1. The raw-JSON guard fired on prose that merely MENTIONS JSON Widening it to search for `{"` anywhere caught real summaries: 'We compared {"role":"assistant"} and {"role":"user"} examples, then agreed to ship Friday.' -> placeholder This org's meetings are about prompts, schemas and APIs, so that is ordinary content, not a malformed response -- and it was being thrown away. Replaced with `_looks_like_json_response`: the candidate must START with a bracket, or follow only a short lead-in that reads like an introduction ("Here are the minutes:"). Prose keeps its summary; a genuine JSON attempt still becomes a placeholder rather than raw text in the PDF. Fixing that exposed a second hole: `_candidate` only stripped a fence with a CLOSING ```. A truncated response never emits one, so ```json fragments read as prose and leaked raw again. It now also strips a dangling opener. 2. The attempt cap could bury a recoverable summary 70 unparseable nested checkpoints exhausted the 64-attempt budget before reaching the top-level `summary`, and salvage returned None. Root-depth checkpoints are now tracked separately and always tried. Several are kept, not just the last: a root-depth checkpoint lands on a KEY as often as a value, and a key alone never parses -- which is why tracking only the last one still failed. 3. Nested objects were stringified into the PDF `[str(x) for x in raw]` turned a salvaged `{"a":"b"}` into the literal "{'a': 'b'}" -- a Python repr presented as a real decision. Non-string entries are now dropped and counted in a warning. Verified: 108 passed (was 105), ruff clean, 40KB worst case still 34.7ms, and an ordinary truncated response still yields title + summary + both decisions + the completed action item. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(meeting): recover minutes from a truncated LLM response
DEVELOPMENT.md told new devs to `cp .env.example .env` six times but never explained which variables need real values, which work as-is, or where to get the ones you must supply. Adds an "Environment configuration" section covering that, plus fixes for gaps found auditing the guide against the code. - .env.example: document vars the code reads but the examples omitted -- ENABLE_DISCORD/ENABLE_WEB/WEB_PORT (bot, src/index.js), THINKING_DEFAULT (llm), MAX_MEETING_MS (meeting). - discord-bot/.env.example: the documented mint command granted 4 scopes, but the bot needs the 10 in scripts/provision-directory-key.sh. A short key boots fine and then 403s on /team, /seed and /my-teams. - DEVELOPMENT.md: `npm run dev:web` requires Docker (it runs `docker compose up -d postgres` against services/team-tracking) and occupies 8001 -- which collides with documentation-system. Neither was documented. - DEVELOPMENT.md: CI lint coverage was described as "every service except documentation-system"; meeting runs `ruff check` but not `ruff format --check`. - .gitignore: every service already ignores its own .env, but add a repo-wide backstop (with !.env.example) so a new service can't leak one, and ignore .venv/ rather than relying on uv's generated self-ignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs: explain env setup and close onboarding gaps
The helper bot's memory is the thread transcript, replayed on each mention. Every non-bot author collapsed into one anonymous `user` turn, and the system prompt named only the current asker — so in a shared thread the model could neither tell who said what nor reason about anyone else's teams. Each user turn is now wrapped in a <msg from="..." teams="..."> tag built from the directory. Identity states stay distinguishable: a confirmed-unlinked speaker gets linked="no", while a failed lookup renders a bare name — a directory hiccup must never claim someone hasn't linked their account. The tag is only trustworthy if a member can't type one, so bodies are escaped (& first, then < >) and names/labels lose attribute-breaking characters. Identity resolution is batched: one shared listTeams per answer plus one lookup per distinct author, with the asker seeded from the already-resolved principal. Every failure degrades that speaker rather than failing the answer. History also grows from 20 to 100 messages (Discord's fetch maximum), bounded by a 48k-char budget on the rendered transcript. Trimming runs before the leading-assistant shave and same-role merge, since it can strand an assistant turn at the head. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(bot): attribute every helper-bot thread turn to its author
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Promotes
staging(a638a22) tomain. 22 commits / 6 PRs since the last release (#132,162e260).What ships
aclose())Release safety
alembic/;meetinghas no DB.MAX_MEETING_MS(meeting) andTHINKING_DEFAULT(llm) both default in code.services/meeting/Dockerfiledrops theffmpegapt layer (Opus decode is now in-process via PyAV).a638a22.Post-merge
us-eastif any reverted tosfo./recordend to end in Discord.🤖 Generated with Claude Code