You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rebuilt 2026-08-13 over 182 open issues and 38 open pull requests. CLAUDE.md
makes reading this a precondition for filing, and it was a title with an empty body
from 2026-08-04 until now — #683 is the issue about that, and this is the answer to
it.
How to use it
Before filing, find the cluster your defect belongs to below and name at least one
issue from it in the body. "Checked, nothing related" is a fine answer; silence is
not. Then come back and add your number to the cluster, and to the spine if it blocks
something.
Two commands before you write a word — the map is a snapshot and the repository is not:
gh issue list --state open --limit 300 --search "<the words in your title>"
gh issue list --state closed --limit 100 --search "<the same words>"
Search the closed ones too.#659 was filed, worked and merged this week as half of #437, which had been closed unfixed six days earlier — that is what #687 is about, and
it is the single most expensive mistake this map exists to prevent. A closed issue in
this repository is often closed by hand, with the evidence in a comment and no
closing pull request, so Closes/Refs in a footer is not a reliable signal either
way.
Every issue below appears exactly once, in the cluster that owns it. 48 of the 182
have a pull request in flight — those are marked, and picking one up is duplicated
work.
Surface parity: what /chat offers that the raw WebSocket and the Public API do not #936 Surface parity: what /chat offers that the raw WebSocket and the Public API do not - the API cannot attach a file and never fills parked, the socket has no environment_id, no ask_user and no compaction notice. Deliverable is a parity table in docs/channels.md that a test holds true
Render a channel chart off the event loop #1429medium Render a channel chart off the event loop - drawn_chart → Pillow render_chart_png runs synchronously in the async turn (2026-09-03 channels audit)
Channels hygiene: seven small items from the 2026-09-03 audit #1434low Channels hygiene: seven small items from the 2026-09-03 audit (Slack payload-URL fetch, router.pyAny, dead list_platforms, client lifecycle, getattr setup hooks, encode_untrusted parity, two duplicated router shapes)
A connector sync ignores sync_mode and duplicates every document on every run #990high A connector sync ignored sync_mode entirely and inserted a duplicate of every document on every run - done, 0.0.216. Review of the fix found the basename collision in existing_document, the delete-before-insert replacement order and the missing updated count
Documents ingested by a connector sync are invisible to every per-document surface #992high A connector sync created no rag_documents row, so its documents were searchable and invisible - done, 0.0.217. Review found the delete that did not delete (delete_document's optional ingestion_service, reachable today for an uploaded document from the Documents tab), the row written after the ingest, app-scoped collections skipped by an equality test, and both models unrecorded
Six answers to "what models and providers exist", and none of them is the authority #923 Six answers to "what models and providers exist", and none of them is the authority - PROVIDERS (27), model_listings.json (20), model_fallbacks.json (7, and not about fallbacks), the genai_prices snapshot, image_models.json and two hand-written tables in docs/models.md, over a run-time authority that is Pydantic AI's own inference. Seven providers answer an empty picker today.
Performance and security (audit 2026-08-19)
The sweep behind these is Audit agenticos 2026-08-19 performance and security. #947 is
the one that is not like the others: it is a live, unauthenticated denial of service.
#843 Resend as an email provider, properly, or not at all — closed, not planned: SMTP is the one way this platform sends mail
#833 ty relaxations are justified by libraries this project does not have — langgraph and deepagents never were, langchain stopped being one in #158 — PR #842
#162 e2e: the cost journey spec flakes on main after passing on the PR
#855lowmake audit fails the required Security Scan job on a single pypi.org timeout — one request per locked distribution, no retry; the network half of the same problem #154 records for e2e
#203low yamlfmt, zizmor and the pre-commit basics read only changed files, and no gate reads the tree
#311high The ai-review reviewer has produced nothing since 2026-08-05, and reports success
#317 CI burns ~8,900 billed Actions minutes a week, a fifth of it on runs nothing cancels
#618 Three frontend tests hand-roll the same source-tree walker — PR #645
#635 check_comments.py descends into every .claude worktree, so a commit waits ~7 minutes — PR #647
#663 Bring app/services/embed_session.py into the 100% coverage + ty gate — PR #674
#862lowtest-frontend goes red on a 5s testTimeout coverage instrumentation cannot meet — a different spec each run, each passing alone; the vitest half of the timing-flake pair #154 and #162 record for e2e — found reviewing PR #624
#915medium Frontend coverage gate red on Node 26: the built-in sessionStorage shadows jsdom's in vitest, so resume.ts's catch loses coverage while every test passes - the sessionStorage half of the localStorage normalization vitest.setup.ts already does; pre-existing from PR #624, found running the gate for #537's trigger-wizard restyle
#1075lowfrontendlayout.test.tsx reaches the network - the root layout's unmocked readBranding() makes the spec depend on what is listening on :8000, so it hangs against a container that accepts connections without answering; green on CI and against a healthy backend, which is why nobody had seen it
#1338lowci Flaky: test_lifespan_drain.py::test_the_lifespan_waits_for_in_flight_background_work fails under xdist with RuntimeError: Event loop is closed and passes on a plain re-run - the test that guards the drain #1137 and #1274 depend on — root cause is the module-global model_catalog._listing_client closed on a dead loop; mitigated in PR #1378 (the drain test stubs the close), the conftest reset that fully fixes it stays open; found working #1364 (duplicate #1381 folded in)
#1545enhancementci Publish agenticos-backend and agenticos-frontend to GHCR on every v* tag (and edge from main); docker compose up pulls them, a clone keeps building through docker-compose.override.yml, the quickstart needs only Docker. Blocked by #1544. Neighbours #1512 (GHCR package visibility), #1415 (SBOM per release), #143 (the docker job leaves ci.yml)
Documentation
#29medium Correct three documentation claims that describe behaviour that does not exist
#840mediumvalidate_webhook_url claims DNS-rebinding protection its callers undo by re-resolving — same shape as #29 and #561; the claim is narrowed rather than delivered — PR #842
#860high MCP OAuth discovery URLs are remote-chosen, and the validated address is not pinned (found reviewing PR #842 — #840's "an operator typed it" reasoning covers the connection URL and the cdp_url, but not mcp_oauth._send, whose every hop the remote server names; this is the delivery half #840 declined) — PR #875
#889low An MCP discovery URL with a bad port answers 500 rather than the refusal — httpx.InvalidURL is not an httpx.HTTPError, so the flow's three catches miss it and #872's fix cannot reach it (pre-existing, found merging main into PR #875)
#861medium A blocked MCP server URL answers a generic 500 instead of naming the refusal — SSRFBlockedError is a ValueError, which no handler maps (pre-existing, found reading the same path) — PR #872
#873medium Importing a spec YAML answers a generic 500 for every kind of mistake in it — a raw pydantic ValidationError is not a RequestValidationError, so no field error reaches the person who edited the file (found sweeping the same defect in PR #872)
#874low An ingestion override with too large a chunk_overlap answers a 500 rather than the form its own docstring says it refuses to (same sweep) — PR #881
#882low A per-field refusal answers with details.errors, which the form cannot mark — the frontend reads details.fields, so all four call sites of that idiom show a sentence and highlight nothing (found fixing #874; #873 added the fourth) — PR #892
#891low The same defect in a third shape: seventeen refusals name one field with a singular details.field and the envelope's message, which no form reads either (deliberately left out of #882 — converging one of eighteen would make it disagree with its siblings) — PR #900
#897low A connector's per-field refusal is flattened to a sentence by validate_config's (bool, str) protocol, so the sync-source wizard marks nothing — the one place #891 could not reach (found fixing #891)
#898low A refused model id is posted back in details={"model": model} — the caller's own submission, serialized and logged — and still marks no input (found fixing #891)
#122 Improve README.md
#212low docs/testing.md documents three fixtures and a layout the suite does not have
#783 README: badges, star tracker and product graphics, styled like pydantic-ai / FastAPI (follow-up to the empty-bodied, closed #122)
#784 Docs: a readability and visual pass, modelled on pydantic-ai / FastAPI (presentation, not content-correctness like #29/#664)
The roadmap
#46 Add Guardrails
#47 Add Planning
#48 Add Context
#49 Add Context manager — PR #775, which also closes #771, #772, #773 and #774
#50 Add Tool Search
#51 Improve WebSearch and WebFetch capabilities — PR #839 adds the web_fetch capability (SSRF-guarded, own web:fetch scope); search-provider spend metering decided against and recorded on the issue, which found #840
#857 Native web search escapes the approval gate, the way native fetch did (found reviewing #839, which fixed the web_fetch half)
#52 Agents evaluation & authoring assistant — plan first
#803 Prune tool_output_limits spills at run end on non-run-scoped workspaces (found reviewing #57)
#787 Add System Reminders: re-inject steering guidance mid-run against instruction fade (harness system_reminders)
#788 Add a Memory capability: persist and recall facts across runs — plan first
#1470 Reshape it into two capabilities a builder picks (memory_files, memory_mem0) and drop the operator console; the native fact store, the origin trust tier and the Memory tab went with it. #1384, #1385 and #1386 were closed as superseded by the same reshape
#1077 A plan does not survive to the conversation's next turn, and the agent denies it exists (found adding the chat plan strip; the same question as #788 about state that outlives a run)
#789 Add a Conversations Search capability: search past transcripts mid-run — plan first (a tool, unlike #159's UI list search). Shipped on #788's branch (#1383) rather than its own: it needs the same run-level fact — who will hear this run — and a second derivation of that is the thing to avoid
#58 Add Image Generation
#59 Add Browseruse
#62 Agents redesign
#63 Admin redesign
#64 MCP redesign
#65 Knowledge Base redesign
#66 Vault redesign
#67 Chat redesign
#68 Organizations redesign
#69 Skills redesign
#70 Artefakty — needs a definition before it can be scoped
#71 Deep Research mode — blocked on #47, #51
#72 Seed Agents
#73 Seed Skills
#150 A first-class public API: organization API keys, so everything the UI does is doable without it
#151 A global assistant in the UI, running the platform through its own API — blocked on #150
#276 Render the delegation tree recursively on the agent map
#168 Issue map: how the open issues relate, and what blocks what
#683 The issue map (#168) has an empty body, so the filing rule that depends on it cannot be followed
#687 24 issues closed with no closing PR since Aug 5; at least two are still broken on main
The spine — what blocks what
Ordered by how much it unblocks. Everything not named here is independent.
1. #440 blocks the whole refusal cluster, and it was closed in error.
Reopened today after verifying on main: PiiRedactionFilter is attached to the root logger at app/core/logging.py:73, where it never sees a record from a module
logger, and setup_logging has exactly one caller — app/main.py:217 — so the Prefect
worker never installs it at all. Every fix in that cluster works the same way: stop
putting the vendor's text in the response, keep it in the logger.exception beside the
raise. That remedy is only sound if the log is scrubbed, and it is not. #676, #681,
#686, #695, #699 and #659 all inherit the assumption; app/services/rag/failures.py:19
states the failure in the present tense and cites the number.
2. #311 is why the sweeps keep finding things. The automated reviewer has produced
nothing since 2026-08-05 while reporting success; eleven pull requests merged on a
sentence that read like a verdict. Until it is fixed the only review before a merge is
a human one. Not a formal blocker on anything — but it is the reason clusters like
"the frontend's own API surface" are found in audits rather than in review.
3. #564 is the structural fix for five symptoms. #13, #30, #546, #553 and #702 are
all the same ~40 hand-rolled backendFetch handlers under src/app/api/**: one
unencoded and unauthenticated (#13), eighteen unencoded (#30), the family swallowing
the backend's refusal reason (#546), omitting no-store (#553), and three echoing the
backend's content type (#702). Collapsing them into platformProxy removes the class. Decide this before PR #668 merges — it already touches about forty of those files
for #546 alone.
4. #150 → #151 and #47 + #51 → #71, both stated in the blocked issue's own
title. #151 (a global assistant driving the platform through its own API) cannot start
before organization API keys exist; #71 (deep research) needs planning and the web
capabilities first.
5. #139 → #282 → #284. One table primitive, then the list-page shell built on it,
then the admin pages standardised onto both. Doing them in the other order means
writing the admin pages twice. Same shape in #136 → #138, #500: one file viewer
before the files page and the header field it needs.
6. #44 + #213 + #53 → #594. Three feature branches — PR #537 (triggers), PR #587
(dashboard layout), PR #624 (onboarding) — that each land alone and then have to agree
with each other on main. #594 is that reconciliation and cannot start until all three
merge.
7. #3, #7 and #12 are acceptance criteria of #39, and PR #634 says Closes #39.
#39's "Done when" list is four items: every surface records a run identically (#3), EmbedSession takes a session factory rather than a session (#12), rate limits apply
to the public surfaces (#7), and docs/channels.md describes what exists. Three of
those are separate open issues. Either the footer should be Refs #39 with the
checkboxes ticked as they land, or #39 should be re-scoped to what #634 actually
finishes. Closes only when the change genuinely finishes it.
Pairs that want one pull request, not two: #7 + #18 (both are one app.add_middleware line in create_app), #147 + #148 (adjacent lines of one
aggregation in repositories/rag_document.py:188-189), #548 → #566 (fix the
disagreement, then collapse the two helpers into one).
8. The connector chain, decided in #938. #984 (a web crawler) is first because it needs no credential and waits on nothing; #985 needs a new SecretKind for an Entra app that nothing else wants yet; #988 blocks Azure Blob and GCS and is the condition #938 attached to them - generalize S3Connector before writing either, not alongside. #990 came before all four and is done (0.0.216), which is what makes a change signal worth naming at all. #982 and #983 are not blockers but every connector on the chain inherits them, so they are worth having before the third one rather than after.
#1544 (runtime frontend config) → #1545 (published images): no frontend image until the hostnames leave the build
The duplicate register — already adjudicated
Recorded so nobody re-litigates them, and so the next filer can see what "the same
bug" has looked like here.
Filed more than once
Survivor
What happened
#636 · #638 · #642
#638
One defect at one line, channels/router.py:728, filed three times in eight days. #642 closed 2026-08-12, #636 closed 2026-08-13.
#554 · #611
#554
mcp-server-list.tsx:360 using window.confirm(), filed twice by the same author eleven days apart from two different sweeps. #611's three unique findings copied onto #554 before closing.
#437 → #659 · #676
—
#437 closed COMPLETED with neither half fixed; both halves re-filed by people who could not find it. #659 is the socket half (PR #675), #676 the run-row half (PR #691). #437 now carries a comment naming them.
#422 → #707
—
Closed for its first finding while a second, live one sat in a comment. #707 is that finding: rag-source-add writing an unvalidated, unowned collection name.
#452 → #451
—
Superseded before it was closed; both now closed.
#617
—
Withdrawn by its author into PR #587 rather than fixed separately.
Pairs that look like duplicates and are not — checked, keep both:
#13 / #30 — #30 is the sweep over eighteen unencoded segments; #13 is the one
instance that is also unauthenticated, which is why it is severity:high where the
sweep is medium. #13 must be in #30's fix, and #30 must not close without it.
#7 / #18 — two different middlewares, both written and never registered. One
call site, two fixes, two sets of acceptance criteria.
#147 / #148 — both make one stats row lie; the fixes are independent and each
body says so. #147 landed in #834 rather than alone, because #158 could not
measure its own before/after while the only stored chunk count was a constant 0.
#656 / #678 — two shapes the i18n guard misses (hyphen, acronym), two pull
requests (#677, #688).
#48 / #49 — context injection against context compaction. Different
capabilities, adjacent names.
What has no scope yet
#70 Artefakty — says so in its own title. Needs a definition before it can be
estimated.
#122, #124, #127, #128 have empty bodies. All four sit on W1 · Aug 3–7, a
milestone eight days past, and are unactionable as written: "Fix Agent Builder
frontend" names no defect and has no acceptance criterion. #168 was the fifth
empty body and is fixed by this rebuild; these four are not. Either write them or
close them — leaving them is what makes the open count untrustworthy.
#52 carries plan first in its title deliberately.
Known-stale references
An issue body's cross-references were true when written. About 78 of the open bodies
cite at least one number that has since closed — mostly the roadmap family citing #16
(un-metered embeddings), which is done. The one that changes a decision is #39's
second checkbox, which describes #16 as live; it is not.
Do not chase the rest. Check a referenced number's state before acting on it:
gh issue view <n> --json state,stateReason,title
Triage state, 2026-08-13
Every open issue now has a type, a milestone, an effort:* label and a row on the VstormOS board. Before today, 84 had no type and 26 — #456 and the whole
2026-08-10 audit batch, #545–#569 — were on no project at all, which made them
invisible to every view the board is read through.
Re-deriving this
gh issue list --state open --limit 300 --json number,title,body,labels,milestone
gh pr list --state open --limit 100 --json number,title,body # then grep the footers
Pass --limit. It defaults to 30 and caps at 100; under a hundred open issues it
silently drops the lowest numbers, which is how #2–#7 once read as closed and a blocker
chain read as already done.
Refs #683, #687.
Filed 2026-08-28, merging the W4 backlog
Thirty pull requests were merged and released as 0.0.316-0.0.343 in one pass. The
five issues above with 13xx numbers came out of that: four from reviewer findings
on branches that were merged anyway, and one flake. Each is in its cluster.
One pull request was not merged: #1128 (#1079, the knowledge retrieval
store crossing worker event loops). Its fix builds a store with its own pool,
which #948 and #12 removed from main, so a mechanical merge either re-adds the
second pool or stops fixing the bug. #1079 stays open with the new shape written
down on it.
Filed 2026-08-29, from the MCP redesign
Three came out of one branch (docs/site-improvements), all in the #64 MCP
redesign cluster and all in the same thread of work:
#1339medium A Mattermost conversation was one channel, not one thread — the agent read every thread in a channel as one conversation
#1340medium Starting OAuth from the catalog answered 404
#1341medium The Builder picker keyed its rows on the catalog entry, so an organization with several connections to one server could bind only one of them — one card per server with an account select, and per-binding personal substitution
#1342feature A member holding two accounts on one service cannot say which an agent speaks as — #1341 declines to guess, and nothing records the choice
#1343low A resumed direct-message run stops speaking as the member who started it — agent_runs records the surface but not whether the conversation held one person
#1391feature An MCP binding is either the organization's account or each person's own — a kind, not a flag. Supersedes #1341's per-binding substitution and #1343's private_to_user; builds on #1342's nomination of a default account. Filed 2026-09-04 — PR #1392
#1442medium Publish and run time each decide MCP prefix collisions, and only publish says so
#1443lowownAccountStatus re-implements the server's account rule on the client, and they disagree on unauthorized
#1444low The personal-service briefing builds a frontend URL in the runner, which a locale-prefixed deployment breaks
#1445low An API run authenticated with a user's own JWT is told nobody is signed in
All four found reviewing PR #1392 before merge; none blocks 0.0.356.
The theme for W6 and W7: a client's security review answered in writing, a mechanism and a test behind every row of a controls matrix (HIPAA §164.312, SOC 2 CC6–CC8), an issue behind every gap. #1424 is the epic and the four-area status; docs/security.md (#1412) is the deliverable the rest feeds.
#1424securitymeta Security programme - the epic
#1412documentationsecurity docs/security.md: threat model, data flows, what is encrypted where, the controls matrix
#1413security Traces sent to Logfire carry full prompts and outputs - a content mode on AgentSpec.observability (same card as #561)
#1414lowsecurityfrontend CodeQL alert #20 in oauth-return.ts - validate or dismiss with a reason (#135 added the path)
#1415cisecurity CodeQL on pull requests, a frontend dependency audit, an SBOM per release (parity via #143)
#1416securityfrontend A real Content-Security-Policy; the widget and hosted page are the framing exception (#702 family)
#1417cisecurity A security pytest marker naming every refusal test, reported per release
#1418security Encrypted connections to Postgres and Redis, and a doctor line
#1419security Generic OIDC sign-in beside Google; honours the sign-up policy (#914). SAML/SCIM and TOTP are in the roadmap note, unfiled
#1420security Per-organization data retention with an audit floor
#1421security Erasure and export of one person's data - what UserService.delete leaves (#941 neighbour)
#1422security Audit trail export and a hash chain (#20, #983 neighbours)
#1423security S3-compatible file storage with server-side encryption beside the local disk
#1448security A HIPAA deployment profile and a doctor --profile hipaa check that proves a running deployment matches it - the configuration half of #1412
Existing security issues scheduled under the same theme rather than moved here: #943 and #1044 (impersonation),
#913, #1047, #1066, #1245, #555, #1072 and #150. Bartek's 2026-09-05 sweep added #1427 (a deactivated member's
channel turn), #1437 (a WebSocket outliving its session), #1438 (identity binding while impersonating) and #1439
(a password change that revokes nothing) — the same theme, filed independently.
W6 and W7 are one milestone as of 2026-09-05, and everything schedulable was drained out of Later into it.
Seven issues stayed behind: #52 and #56 (themes planned in the roadmap note), #70 (unscoped), #71 and #151
(blocked), #54 (needs an architectural decision for the realtime half), #985 (needs the tenant-consent answer).
Rebuilt 2026-08-13 over 182 open issues and 38 open pull requests.
CLAUDE.mdmakes reading this a precondition for filing, and it was a title with an empty body
from 2026-08-04 until now — #683 is the issue about that, and this is the answer to
it.
How to use it
Before filing, find the cluster your defect belongs to below and name at least one
issue from it in the body. "Checked, nothing related" is a fine answer; silence is
not. Then come back and add your number to the cluster, and to the spine if it blocks
something.
Two commands before you write a word — the map is a snapshot and the repository is not:
Search the closed ones too. #659 was filed, worked and merged this week as half of
#437, which had been closed unfixed six days earlier — that is what #687 is about, and
it is the single most expensive mistake this map exists to prevent. A closed issue in
this repository is often closed by hand, with the evidence in a comment and no
closing pull request, so
Closes/Refsin a footer is not a reliable signal eitherway.
Every issue below appears exactly once, in the cluster that owns it. 48 of the 182
have a pull request in flight — those are marked, and picking one up is duplicated
work.
The clusters
A refusal that quotes the vendor
mediumStop logging expected refusals as application errorsmediumMake an audit write failure loud, and type record_auditmediumRefuse a malformed sealed payload without naming its contentshighPiiRedactionFilter redacts nothing: it is on the root logger, and the worker never installs itmediumDashboard error frame sends str(e) of a provider exception to the client — PR fix(chat): stop sending a provider's own text to the chat panel #675mediumA failed agent run still stores the model client's exception text on the run row — PR fix(agents): store the refusal on a failed run, not the provider #691mediumThe chat tool_result frame relays a search provider's exception text to the client — PR Name the tool that failed, not the search provider's error #694highMCP OAuth refusals interpolate an httpx error, so a request URL can reach the user — PR fix(mcp): stop OAuth refusals quoting the upstream client #693mediumThe run transcript stores a search provider's exception text on a tool-call rowmediumA delegated run's row still stores the subagent library's exception text — PR fix(agents): compose a delegated run's stored error, never store the library's #816mediumcheck_task and wait_tasks hand a delegate's provider text to the parent's transcript — PR fix(agents): keep a delegate's provider text out of a status answer #832Who may do what
An app admin can suspend, demote and delete their own account, unguarded at both layers #941
mediumsecurityAn app admin can suspend, demote and delete their own account - no guard at either layer, and two of the three take one click with no confirmation. Same family as change_role can mint a second Owner, bypassing transfer_ownership #672 and change_role lets an Admin demote a peer Admin that remove refuses to remove #700Impersonation mints a bearer token onto the clipboard, and nothing it does is attributable to the admin #943
mediumsecurityImpersonation mints a bearer token onto the clipboard, carries no actor claim, and cannot be ended - so nothing done with it is attributable to the adminImpersonation should be an endable session, not a token on the clipboard #1044
enhancementsecurityImpersonation as an endable session, not a token on the clipboard — the flow half of Impersonation mints a bearer token onto the clipboard, and nothing it does is attributable to the admin #943 — PR feat(admin): make impersonation an endable session, not a token on the clipboard #1435An open WebSocket outlives the revocation of the session that opened it #1437
mediumsecurityAn open WebSocket outlives the revocation of the session that opened it — found reviewing Impersonation should be an endable session, not a token on the clipboard #1044chat: a normal (non-impersonation) session's WebSocket is not closed by sign-out-everywhere (no sid bind) #1501
mediumsecurityA normal (non-impersonation) session's WebSocket is not closed by sign-out-everywhere — nosidbinds an ordinary access token to a session row, so it hangs onis_activealone — found implementing An open WebSocket outlives the revocation of the session that opened it #1437Identity-binding routes are allowed while impersonating, so an admin can attach their own chat identity to somebody's account #1438
mediumsecurityIdentity-binding routes (channel link, MCP OAuth) are allowed while impersonating — found reviewing Impersonation should be an endable session, not a token on the clipboard #1044Organization MCP OAuth starts are allowed while impersonating, binding an admin grant as a member #1490
mediumsecurityThe organization MCP OAuth starts are the org half of Identity-binding routes are allowed while impersonating, so an admin can attach their own chat identity to somebody's account #1438, left open by its personal-only fix — found fixing Identity-binding routes are allowed while impersonating, so an admin can attach their own chat identity to somebody's account #1438Manual personal MCP token entry is allowed while impersonating, storing an admin token as a member's #1492
mediumsecurityManual personal MCP token entry is unguarded under impersonation — the manual-token half of Identity-binding routes are allowed while impersonating, so an admin can attach their own chat identity to somebody's account #1438 — found fixing Identity-binding routes are allowed while impersonating, so an admin can attach their own chat identity to somebody's account #1438Manual org MCP token entry is neither refused nor fully audited while impersonating #1521
mediumsecurityManual organization MCP token entry is neither refused nor fully audited under impersonation — the org manual-token corner alongside Organization MCP OAuth starts are allowed while impersonating, binding an admin grant as a member #1490/Manual personal MCP token entry is allowed while impersonating, storing an admin token as a member's #1492 — found fixing Manual personal MCP token entry is allowed while impersonating, storing an admin token as a member's #1492Changing a password from Settings does not revoke the account's other sessions #1439
mediumsecurityChanging a password from Settings does not revoke the other sessions; only the email reset does — found reviewing Impersonation should be an endable session, not a token on the clipboard #1044auth: refresh-token rotation has no reuse/replay detection (kill-family on a spent token) #1519
enhancementsecuritypriority:lowRefresh-token rotation has no reuse/replay detection (kill-family when a spent token returns) — a defense-in-depth pattern the project never had, orthogonal to chat: a normal (non-impersonation) session's WebSocket is not closed by sign-out-everywhere (no sid bind) #1501, not a regression — found implementing chat: a normal (non-impersonation) session's WebSocket is not closed by sign-out-everywhere (no sid bind) #1501PATCH /users/me with {"password": null} returns 500 instead of a 4xx #1497
lowMalformed{"password": null}onPATCH /users/mereturns 500, not a 4xx — found fixing Changing a password from Settings does not revoke the account's other sessions #1439, pre-existing input-validation defect in the same branchSharing a conversation outside the organization is accepted and unreadable #930
mediumSharing a conversation outside the organization is accepted and unreadable - the share resolves any deployment user, the read refuses on the tenant first (found reading the path for The Share conversation dialog: a member picker instead of an email box, room to breathe, and icons on view/edit #931)Access-check spec.skill_ids at publish, like every other reference #6
highAccess-check spec.skill_ids at publish, like every other referenceOAuth sign-in drops the ?returnTo= deep link #135 OAuth sign-in drops the ?returnTo= deep link
Invitations never reach EXPIRED: the sweep has no caller, and a timed-out one is marked revoked #456
lowInvitations never reach EXPIRED: the sweep has no caller, and a timed-out one is marked revoked — PR Expire a stale invitation instead of leaving it pending #667InviteLinkCreate.role is unvalidated — an Owner can mint an owner-granting share link #551
mediumInviteLinkCreate.role is unvalidated — an Owner can mint an owner-granting share link — PR Validate the role an invite link offers #671Agent draft can't be published by a builder who can't add a model #591
mediumAgent draft can't be published by a builder who can't add a model — PR feat(onboarding): first-run tour, guided flows #624change_role can mint a second Owner, bypassing transfer_ownership #672
mediumchange_role can mint a second Owner, bypassing transfer_ownership — PR fix(permissions): stop change_role handing out the owner role #697Invitation role ceiling is keyed on the literal 'admin', so only built-in Admins are capped #696
mediumInvitation role ceiling is keyed on the literal 'admin', so only built-in Admins are capped — PR fix(permissions): cap the invitable role by what the requester holds #1019, mergedThe role pickers offer roles the caller cannot assign, and the service refuses them after the fact #1028
lowfrontendThe role pickers offer roles the caller cannot assign, and the service refuses them after the fact — the client half of Invitation role ceiling is keyed on the literal 'admin', so only built-in Admins are capped #696 and change_role lets an Admin demote a peer Admin that remove refuses to remove #700, found reviewing fix(permissions): cap the invitable role by what the requester holds #1019change_role lets an Admin demote a peer Admin that remove refuses to remove #700
lowchange_role lets an Admin demote a peer Admin that remove refuses to removeinvite_only cannot honour a shareable link with no address or domain #916
lowinvite_only cannot honour a shareable link with no address or domain: the register request carries no token, so the policy can only match an invitation against the submitted address (found reviewing fix(builder): configure an agent in one place, and stop guessing at what a reader sees #914, which added the sign-up policy)ASK_ALL is not restored on resume, so a rejected tool call can execute #1326
highsecurityASK_ALLis not restored when a parked run resumes, so a tool gated only by the conversation's approval mode and then rejected can reach its handler (found reviewing feat(approvals): a per-conversation approval mode in the chat #1295, which added the mode)A conversation cannot be shared past the first page of members #1335
highfrontendA conversation cannot be shared past the first page of members: the picker The Share conversation dialog: a member picker instead of an email box, room to breathe, and icons on view/edit #931 made the only path fetches one page of 50 and Sharing a conversation outside the organization is accepted and unreadable #930's tightening removed the address field that used to be the escape hatchChannels and bots
Surface parity: what /chat offers that the raw WebSocket and the Public API do not #936 Surface parity: what
/chatoffers that the raw WebSocket and the Public API do not - the API cannot attach a file and never fillsparked, the socket has noenvironment_id, noask_userand no compaction notice. Deliverable is a parity table indocs/channels.mdthat a test holds trueRefuse a Telegram webhook that carries no secret #4
highRefuse a Telegram webhook that carries no secretOther channels setup (websocket, frontend, etc.) #39 Other channels setup (websocket, frontend, etc.) — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634
Telegram setup #42 Telegram setup
Slack setup #43 Slack setup
Workspaces: a user-scoped workspace from Slack or Telegram is invisible to the linked account #131 Workspaces: a user-scoped workspace from Slack or Telegram is invisible to the linked account
Offer the widget's WebSocket as an integration, not only as documentation #516 Offer the widget's WebSocket as an integration, not only as documentation — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634
A hosted chat page: reach an agent by link, with no login and no site of your own #517 A hosted chat page: reach an agent by link, with no login and no site of your own — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634
Visual map: four directions around the agent, and a node per subagent #518 Visual map: four directions around the agent, and a node per subagent
Telegram polling and Mattermost webhook bypass parse_incoming, dropping attachments #547
highTelegram polling and Mattermost webhook bypass parse_incoming, dropping attachments — PR fix(channels): parse every inbound transport with one parser #698Three channel webhook routes drift on a missing secret: 500 (Slack) vs 403 (others) #555
mediumThree channel webhook routes drift on a missing secret: 500 (Slack) vs 403 (others)chat_type vocabulary differs per adapter, so the column holds mixed vocabularies #556
mediumchat_type vocabulary differs per adapter, so the column holds mixed vocabulariesLift the channel transport boilerplate into base.py helpers #565
lowLift the channel transport boilerplate into base.py helpersA widget update sending theme: null answers 500 on a NOT NULL column #637
lowA widget update sending theme: null answers 500 on a NOT NULL column — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634A channel bot past 200 messages is reminded of the thread's first 200, not its last #638
mediumA channel bot past 200 messages is reminded of the thread's first 200, not its last — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634A group channel refuses every sender who never linked an account, and require_link decides nothing #639 A group channel refuses every sender who never linked an account, and require_link decides nothing — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634
Two copies of "an anonymous surface runs as its publisher": embed_session and channels/mentions #640 Two copies of "an anonymous surface runs as its publisher": embed_session and channels/mentions — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634
A channel thread stays visible to somebody removed from the channel #641 A channel thread stays visible to somebody removed from the channel
The cookie banner covers Send on a hosted page, and its copy is untrue there #644
mediumThe cookie banner covers Send on a hosted page, and its copy is untrue there — PR feat(channels): surface parity, the socket as an integration, and a hosted chat page #634Every agenticos cmd channel-* command fails: the CLI builds ChannelBotService with no organization, and three of them report it as "Bot not found" #1350
highEveryagenticos cmd channel-*command fails - the CLI builds ChannelBotService with no organization, and three of the five report it as "Bot not found" (found setting a Slack bot up from scratch)A Slack bot whose Socket Mode connection never starts looks healthy on /channels - the only evidence is a log line #1351
mediumfrontendA Slack bot whose Socket Mode connection never starts looks healthy on /channels - the only evidence is a log line. Narrowed by PR docs: rebuild the site, add the galleries, and close eleven issues #1344, which badges an absentxapp-token; a present-but-rejected one is still silentslack-sdk is declared without the [socket-mode] extra, so Socket Mode works only while aiohttp arrives transitively #1352
lowdependenciesslack-sdk is declared without the[socket-mode]extra, so Socket Mode works only while aiohttp arrives transitively - and the guarded import means no test would catch its removalA bot mentioned in a thread it has no record of answers as if the thread were empty - nothing backfills the earlier turns #1353
mediumA bot mentioned in a thread it has no record of answers as if the thread were empty - nothing backfills the earlier turns, andread_channel_historyis bound to the channel rather than the thread (found verifying PR docs: rebuild the site, add the galleries, and close eleven issues #1344 in a real workspace)Mattermost webhook transport answers every post, unlike the stream transport #662
mediumMattermost webhook transport answers every post, unlike the stream transport — PR fix(channels): gate a Mattermost webhook post the way the stream is #685A Mattermost webhook bot has no server URL, so its attachments cannot be fetched #692
mediumA Mattermost webhook bot has no server URL, so its attachments cannot be fetchedRefuse a deactivated member's channel turn instead of running it at their old role #1427
highsecurityRefuse a deactivated member's channel turn instead of running it at their old role -_membership_contextreads the role offmember_repo.getwherepublisher_contextwas hardened toget_active; not fixed by PR feat(spec)!: an MCP binding says whose account it speaks through #1392, land after it (2026-09-03 channels audit)Bound the router's chat-lock and rate-bucket maps, which grow per Slack message #1428
mediumBound the router's_chat_locks/_rate_buckets, which grow per top-level Slack message and were named in Add row locks to four check-then-act races #17 but never evicted (2026-09-03 channels audit)Render a channel chart off the event loop #1429
mediumRender a channel chart off the event loop -drawn_chart→ Pillowrender_chart_pngruns synchronously in the async turn (2026-09-03 channels audit)One supervised reconnect loop for the three adapters; Telegram retries a bad token forever #1430
mediumOne supervised reconnect loop for the three adapters; Telegram has no config-error branch and retries a bad token forever (2026-09-03 channels audit)Re-stamp the Telegram polling heartbeat so a quiet bot does not read unknown #1431
lowRe-stamp the Telegram polling heartbeat - the Telegram half A Slack bot whose Socket Mode connection never starts looks healthy on /channels - the only evidence is a log line #1351 named but did not do (2026-09-03 channels audit)Make the channel chart dispatch exhaustive over ChartType, and fix the skill's claim that the renderer is gone #1432
lowAnareachart renders as a line chart in channels, and the channel-bot skill sayschart_png.pyis gone (2026-09-03 channels audit)Couple jwt_linked to require_link, or retire the mode value that decides nothing #1433
lowCouplejwt_linkedtorequire_link, or retire the mode value that decides nothing since A group channel refuses every sender who never linked an account, and require_link decides nothing #639 (2026-09-03 channels audit)Channels hygiene: seven small items from the 2026-09-03 audit #1434
lowChannels hygiene: seven small items from the 2026-09-03 audit (Slack payload-URL fetch,router.pyAny, deadlist_platforms, client lifecycle,getattrsetup hooks,encode_untrustedparity, two duplicated router shapes)Gate /new, /project and /unlink on the same link admission as a turn #1455
severity:lowGate /new, /project and /unlink on the same link admission as a turn - pre-existing since A group channel refuses every sender who never linked an account, and require_link decides nothing #639, found reviewing fix(channels): make the jwt_linked mode require a link on its own #1454Refuse a deactivated linked sender before attachments are stored and transcribed #1456
severity:lowsecurityRefuse a deactivated linked sender before attachments are stored and transcribed - moves fix(channels): refuse a deactivated member's channel turn #1436's run-level refusal earlier, found reviewing fix(channels): make the jwt_linked mode require a link on its own #1454A crashed channel turn leaves its attachments orphaned in chat_files #1503
lowA crashed channel turn leaves its attachments orphaned inchat_files— found unifying the router turn paths (Unify the mention and default turn paths' refusal-and-crash handling in the channel router #1459), same A successful channel turn never links its ChatFile rows to a message #690 classThread backfill under jwt_linked quotes unlinked authors into the prompt #1457
severity:lowThread backfill under jwt_linked quotes unlinked authors into the prompt - generalises the whitelist backfill filter, found reviewing fix(channels): make the jwt_linked mode require a link on its own #1454Unify the mention and default turn paths' refusal-and-crash handling in the channel router #1459
enhancementUnify the mention and default turn paths' refusal-and-crash handling - the try/except half split out of Channels hygiene: seven small items from the 2026-09-03 audit #1434 (2026-09-03 channels audit)A room thread nobody linked an account in is writable by any member #701
lowA room thread nobody linked an account in is writable by any memberFiles a turn writes
data:URI, so it costs no request at allmediumChat uploads are capped at a hardcoded 10MB while every setting says 50mediumAn answered ask_user question leaves no trace in the transcriptmediumA non-mention channel message with attachments stores every file twice — PR fix(channels): fetch a message's attachments once, not per path #684lowA channel turn refused before the run orphans its ChatFile rows — PR Delete the files a refused channel turn stored #689lowA successful channel turn never links its ChatFile rows to a message — PR Link a channel turn's files to the message it wrote #703lowA caption-less image on a workspace-less agent records no user turnmediumA chat turn can link another user's file to its own messagelowAn attachment the router cannot read is silence to the model, so the agent denies the file (found working A caption-less image on a workspace-less agent records no user turn #704)lowA malformed file id off the socket reads as a server error, not a refusal (found working A chat turn can link another user's file to its own message #706)lowA blank user turn is still written by the streaming socket when files arrive with no words (found reviewing fix(chat): name a caption-less turn's files in its user message #747, the A caption-less image on a workspace-less agent records no user turn #704 fix) — PR Name a blank streaming turn's files in its user message #751, stacked on fix(chat): name a caption-less turn's files in its user message #747The frontend's own API surface
highEncode the avatar proxy's path segmentmediumEncode every interpolated segment in the hand-rolled proxieshighProxy routes discard the backend's refusal reason, showing a generic 'Backend API error' — PR fix(api): answer a refusal with the backend's reason, not a generic #668mediumThe backendFetch route family omits no-store, so a refetch-after-write can be served stalemediumrating-buttons hand-rolls two fetch calls instead of a lib client and a hooklowCollapse the backendFetch forwarder family (~40 routes) into platformProxylowToast layer still reads .message off ApiError at ~25 sites, bypassing BFF code resolutionhighThree proxy routes echo the backend's content type, and one of them can serve HTMLenhancementfrontendNEXT_PUBLIC_*are build arguments, so one frontend image carries one deployment's hostnames - readPUBLIC_API_URL,PUBLIC_WS_URL,PUBLIC_SITE_URL,CHAT_MAX_UPLOAD_SIZE_MBandOAUTH_PROVIDERSat runtime through a context, the wayBrandingProviderdoes;NEXT_PUBLIC_RAG_ENABLEDis read by nothing. Blocks Publish the backend and frontend images to GHCR on every release, and make docker compose up pull them #1545English in every locale
highDOM keyboard constants parked in en.json — a translator can silently break Enter-to-submit — PR fix(chat): compare DOM key literals, not catalog values #669lowBFF route payloads and the workspace step detail reach the toast in English — PR fix(i18n): resolve BFF refusal codes and step details in the locale #654lowfix(frontend): Polish diacritics fall back to the system font — vendored subsets are latin only — PR fix(frontend): render Polish diacritics in the brand fonts #652lowTwelve local next-intl mocks model t but not t.richlowA Polish page still declares itself English: is hard-coded — PR fix(i18n): declare <html lang> from the active locale #646lowtimeAgo formats a date older than a week as en-US in every locale — PR fix(i18n): format timeAgo's date fallback in the active locale #651lowformatDate, formatDateTime and formatCurrency still format as en-US in every locale — PR Format an absolute date in the active locale #673lowcheck-i18n misses prose whose first word is hyphenated ("Sign-in failed") — PR fix(i18n): read a hyphenated first word as prose, not a label #677lowcheck-i18n misses prose whose first word is an acronym ("API keys are stored in the vault") — PR fix(frontend): detect prose after leading acronyms #688Ingestion and retrieval
Sync source credentials are pasted into a JSONB column, encrypted by the one second mechanism the vault was meant to replace #937
securitySync source credentials are pasted intosync_sources.configand encrypted byapp/core/crypto.py- the one second mechanism the vault was meant to replace, surviving becauseorganization_idis nullable. Blocks Which sync connectors after Google Drive and S3 - decide the list and the order #938Which sync connectors after Google Drive and S3 - decide the list and the order #938 Which sync connectors after Google Drive and S3 - decided, PR docs(rag): decide which sync connectors follow Drive and S3 #989: the list is cut to Sync connector: a web crawler, from a root URL or a sitemap #984 → Sync connector: SharePoint and OneDrive, on an Entra app credential #985 → Sync connector: Confluence spaces #986 → Sync connector: a git repository's documentation #987, then Generalize S3Connector to an object store, before Azure Blob and GCS #988; Notion, Slack and email are decided against. The cross-connector permissions answer is in
docs/file-processing.md, and A sync source's wizard never says who will be able to read what it ingests #982/A sync source is created, repointed and deleted with no audit entry #983 are what it still owesA connector sync ignores sync_mode and duplicates every document on every run #990highA connector sync ignoredsync_modeentirely and inserted a duplicate of every document on every run - done, 0.0.216. Review of the fix found the basename collision inexisting_document, the delete-before-insert replacement order and the missingupdatedcountDocuments ingested by a connector sync are invisible to every per-document surface #992highA connector sync created norag_documentsrow, so its documents were searchable and invisible - done, 0.0.217. Review found the delete that did not delete (delete_document's optionalingestion_service, reachable today for an uploaded document from the Documents tab), the row written after the ingest,app-scoped collections skipped by an equality test, and both models unrecordedA synced file's error row is never retired by the sync that succeeds #996
lowA synced file's error row is never retired by the sync that succeeds - retirement matchesvector_document_idand a failed parse writes none. Needs asource_pathcolumn onrag_documents: matching by filename would delete the other row when a bucket holds two keys of one basename, which is the collision A connector sync ignores sync_mode and duplicates every document on every run #990 removed on the vector side. The column also lets the Documents tab say where a document came from (found reviewing fix(rag): record a document row for what a connector sync ingests #995)The local-directory sync writes its document row after indexing, and only on success #997
lowThe local-directory sync writes its document row after indexing and only on success, so it keeps the exposure Documents ingested by a connector sync are invisible to every per-document surface #992 removed from the connector path and a file that failed to parse leaves no row and no reason (found reviewing fix(rag): record a document row for what a connector sync ingests #995)Sync connector: a web crawler, from a root URL or a sitemap #984 A web crawler connector, from a root URL or a sitemap - no credential, first of the five; inherits validate_webhook_url claims DNS-rebinding protection its callers undo by re-resolving #840/MCP OAuth discovery URLs are remote-chosen, and the validated address is not pinned #860's pinned client and makes Paginate GET /rag/documents, and stop full-scanning on ingest #27 matter sooner
Sync connector: SharePoint and OneDrive, on an Entra app credential #985 SharePoint and OneDrive, on a new
SecretKindfor an Entra app - the largest gap on the list, and the sharpest case of A sync source's wizard never says who will be able to read what it ingests #982's question (tenant-wide consent)Sync connector: Confluence spaces #986 Confluence spaces, on a page's
version.numberas the change signalSync connector: a git repository's documentation #987 A git repository's documentation - the commit sha makes incremental sync exact; inherits validate_webhook_url claims DNS-rebinding protection its callers undo by re-resolving #840/MCP OAuth discovery URLs are remote-chosen, and the validated address is not pinned #860 for the clone URL
Generalize S3Connector to an object store, before Azure Blob and GCS #988 Generalize
S3Connectorto an object store before writing Azure Blob and GCS - the condition Which sync connectors after Google Drive and S3 - decide the list and the order #938 attached to themA sync source's wizard never says who will be able to read what it ingests #982
frontendThe sync-source wizard never says who will be able to read what a source ingests - the model is written down in Which sync connectors after Google Drive and S3 - decide the list and the order #938, and this is the sentence that states it where the collection is chosenA sync source is created, repointed and deleted with no audit entry #983
securityA sync source is created, repointed and deleted with no audit entry, so "who bound this credential to this collection" has no answer after the fact (found deciding Which sync connectors after Google Drive and S3 - decide the list and the order #938)Tabs for the RAG pages: Reusable integrations as /rag's third tab, and a knowledge base's three sections as three #939Tabs for the RAG pages - done, 0.0.213The RAG dialogs: a markdown editor for the image prompt, one width scale, and a mark on the parser choices #940
lowThe RAG dialogs:MarkdownEditorfor the image prompt, one width scale (the same form gets 512px in create and 768px elsewhere), and a mark on the parser choicesMove blocking file work off the request event loop #25mediumMove blocking file work off the request event loop - done (offloadedsave/loadand the worker's hash/stat;deletewas missed, now rag: LocalFileStorage.delete blocks the event loop during bulk teardown #1294)Paginate GET /rag/documents, and stop full-scanning on ingest #27
mediumPaginate GET /rag/documents, and stop full-scanning on ingestAdd a real reranker to RAG retrieval #142 Add a real reranker to RAG retrieval — PR feat(rag): add a real reranker to RAG retrieval #911
rag: binding an embedding secret skips the caller's secret-view check #912
mediumsecurityrag: binding an embedding secret skips the caller's secret-view check — found reviewing Add a real reranker to RAG retrieval #142rag: per-collection resolution picks a KB by non-unique collection_name #913
mediumsecurityrag: per-collection resolution picks a KB by non-unique collection_name — found reviewing Add a real reranker to RAG retrieval #142rag: chunk_count is never recorded, so every document claims 0 chunks #147
mediumrag: chunk_count is never recorded, so every document claims 0 chunks — PR feat(rag): chunk with our own splitters and drop the langchain tree #834rag: KB stats count status "completed", but ingestion writes "done", so indexed_count is always 0 #148
mediumrag: KB stats count status "completed", but ingestion writes "done", so indexed_count is always 0 — PR fix(rag): count the status the pipeline writes, not one nothing writes #957, which introducesDocumentStatusso the writer and the reader name one memberThe ingestion flow's _update_status has a branch nothing can reach, and a status parameter with one value #956
lowThe ingestion flow's_update_statushas an unreachabledonebranch and a status parameter all four callers pass one value to — found fixing rag: KB stats count status "completed", but ingestion writes "done", so indexed_count is always 0 #148Replace langchain-text-splitters with our own, and drop the langchain tree #158 Replace langchain-text-splitters with our own, and drop the langchain tree — PR feat(rag): chunk with our own splitters and drop the langchain tree #834, with rag: chunk_count is never recorded, so every document claims 0 chunks #147 in the same branch
Two RAG document-lookup helpers disagree on precedence; get_documents has no ORDER BY #548
highTwo RAG document-lookup helpers disagree on precedence; get_documents has no ORDER BY — PR Give RAG document lookup one precedence and a stable order #670PyMuPDF OCR fallback nests an event loop, so a scanned PDF indexes empty #550
highPyMuPDF OCR fallback nests an event loop, so a scanned PDF indexes empty — PR fix(rag): await the OCR describer instead of nesting an event loop #666One RAG document-lookup helper; collapse the 3-4 full-collection scans per ingest #566
lowOne RAG document-lookup helper; collapse the 3-4 full-collection scans per ingestrag-source-add writes an unvalidated, unowned collection name from the CLI #707
mediumrag-source-add writes an unvalidated, unowned collection name from the CLIDispose the vector store's engine, or stop building one per ingested document #948
highThe ingestion worker builds a pooled vector-store engine per document and disposes none, so a large batch reachesmax_connections— PR fix(rag): dispose the vector store each ingestion flow builds #960Insert a document's chunks in one statement, not one per chunk #950
mediumA document's chunks are inserted one statement each, so a 200-page PDF is one to three thousand round trips — PR perf(rag): write a document's chunks in batches, not one statement each #962A document's embeddings are all held in memory at once, so a long PDF is hundreds of megabytes in the worker #963
lowA document's embeddings are all held in memory at once, so a long PDF is hundreds of megabytes in the worker — found reviewing PR perf(rag): write a document's chunks in batches, not one statement each #962The in-process RAG fallback handlers have no caller, and each leaks the store #948 fixed #959
lowThe in-process RAG fallback handlers have no caller and each carries Dispose the vector store's engine, or stop building one per ingested document #948's undisposed store — found fixing Dispose the vector store's engine, or stop building one per ingested document #948rag: teardown unlinks stored files before the request commits #1293
mediumrag: teardown unlinks stored files before the request commits, so a failed commit leaves rows pointing at missing uploads — found reviewing fix(rag): unlink a dropped collection's stored uploads #1284 (PR for DELETE /rag/collections/{name} orphans the stored upload files #1265)rag: LocalFileStorage.delete blocks the event loop during bulk teardown #1294
mediumrag:LocalFileStorage.deleteblocks the event loop, and bulk teardown deletes every file in one turn — found reviewing fix(rag): unlink a dropped collection's stored uploads #1284; thedeletecase Move blocking file work off the request event loop #25 missedrag: delete paths drop vector tables and remove vectors before the request commits #1347
mediumrag: delete paths drop vector tables and remove vectors before the request commits — same rollback class as rag: teardown unlinks stored files before the request commits #1293, on the store's own engine; found finishing rag: teardown unlinks stored files before the request commits #1293rag: deferred teardown file unlinks are in-process, not durable across a restart #1349
lowrag: deferred teardown file unlinks are in-process (spawn_after_commit), so a worker restart mid-drain orphans files — recoverable leak, the durable (Prefect) half of rag: teardown unlinks stored files before the request commits #1293; sibling of Org-teardown cleanup is lost if the process dies after commit (spawn_after_commit is not durable) #1274rag/org-teardown: collection drop's reference re-check is not serialized with collection claims (TOCTOU) #1355
mediumrag: the teardown collection-drop'slist_by_collection_namere-check is not serialized with collection claims (TOCTOU) — the drop can still delete a name reclaimed between the re-read and theDROP TABLE; found reviewing rag: delete paths drop vector tables and remove vectors before the request commits #1347, pre-existing in Org-teardown cleanup is lost if the process dies after commit (spawn_after_commit is not durable) #1274's org-purge too; needs an advisory lock across both drops and thePOST /rag/collections/{name}claim path; rooted in rag: per-collection resolution picks a KB by non-unique collection_name #913rag: two in-request collection-drop paths bypass the durable locked teardown #1359
mediumrag: two in-request collection-drop paths bypass the durable locked teardown —DELETE /rag/collections/{name}(no rag: per-collection resolution picks a KB by non-unique collection_name #913 re-check, no lock) and_purge_personal_collections(re-check, no lock), both dropping the table before the request commits; found reviewing rag/org-teardown: collection drop's reference re-check is not serialized with collection claims (TOCTOU) #1355, which locked the durable path but not these; siblings of rag: delete paths drop vector tables and remove vectors before the request commits #1347/rag: deferred teardown file unlinks are in-process, not durable across a restart #1349/rag/org-teardown: collection drop's reference re-check is not serialized with collection claims (TOCTOU) #1355/rag: per-collection resolution picks a KB by non-unique collection_name #913rag: deferred vector-table drop leaks across tenants via name reuse (and a default-KB clear regression) #1362
highsecurityrag: the whole deferred-drop arc (rag: delete paths drop vector tables and remove vectors before the request commits #1347/rag: deferred teardown file unlinks are in-process, not durable across a restart #1349/rag/org-teardown: collection drop's reference re-check is not serialized with collection claims (TOCTOU) #1355/rag: two in-request collection-drop paths bypass the durable locked teardown #1359) frees a collection name before the DROP runs, so an attacker re-creating the same explicit name in the commit-to-drop window hasCREATE TABLE IF NOT EXISTSadopt the victim's populated table and reads its chunks; the rag/org-teardown: collection drop's reference re-check is not serialized with collection claims (TOCTOU) #1355 lock only serializes, it doesn't stop the claim winning the race. Also folds the default-KB clear regression. Needs a tombstone/generation (a mechanism decision, like Org-teardown cleanup is lost if the process dies after commit (spawn_after_commit is not durable) #1274). Found by codex on fix(rag): route the last two in-request collection drops through the durable teardown #1361; the arc's merge should hold until this is decidedrag: teardown reservations need a stuck-tombstone sweep, and the default-clear has an ingest window #1364
lowrag: teardown reservations (rag: deferred vector-table drop leaks across tenants via name reuse (and a default-KB clear regression) #1362) have no stuck-tombstone sweep — a name whose drop permanently fails is blocked for good — and the default-KB clear has a same-tenant ingest-vs-drop window; both fail-safe, found reviewing rag: deferred vector-table drop leaks across tenants via name reuse (and a default-KB clear regression) #1362rag: serialise teardown against concurrent writes (upload, sync) end to end #1382
lowrag: teardown is not fully serialised against concurrent writes - the worker sync/retry ingestion paths don't consult the reservation (reachable for a default+sync clear, latent while nothing sets is_default), the reservation is created after the row delete so the upload guard is best-effort, and that guard drops its lock to avoid inverting delete's lock order; found reviewing fix(rag): reap stuck teardown reservations, guard uploads, and clear a dropped default #1378 (the rag: teardown reservations need a stuck-tombstone sweep, and the default-clear has an ingest window #1364 PR)rag: claim and org purge take the teardown and organizations locks in opposite order (ABBA deadlock) #1387
lowrag:claimand orgpurgetake the teardown lock and theorganizationsrow lock in opposite order — a KB create naming an existing collection, racing a purge of that collection's org, deadlocks (ABBA); pre-existing since rag: deferred vector-table drop leaks across tenants via name reuse (and a default-KB clear regression) #1362's claim lock, split from rag: serialise teardown against concurrent writes (upload, sync) end to end #1382, found reviewing fix(rag): reap stuck teardown reservations, guard uploads, and clear a dropped default #1378rag: teardown lock-ordering residuals (gap-collection D↔K, user-delete U↔T) need a serialization point #1389
lowrag: teardown lock-ordering residuals surviving rag: claim and org purge take the teardown and organizations locks in opposite order (ABBA deadlock) #1387's fix — a gap collection born betweenpurge's snapshot and its org lock can D↔K-deadlock with a concurrent KB delete/drop and is dropped-but-not-reserved (rag: deferred vector-table drop leaks across tenants via name reuse (and a default-KB clear regression) #1362 window reopened for that subset); and user-delete'susersFOR UPDATE inverts against a personal-KB create's teardown lock (U↔T); both narrow and self-resolving, both need a serialization point/outbox; split from rag: serialise teardown against concurrent writes (upload, sync) end to end #1382, found in rag: claim and org purge take the teardown and organizations locks in opposite order (ABBA deadlock) #1387's adversarial reviewUnattended runs
highDrain background work before tearing the process downmediumScheduled trigger fire() drops last_run_id and fails the flow on a model error — PR fix(triggers): record a model-errored fire instead of failing the flow #589mediumrun_now executes the agent synchronously in the HTTP request (504 + double-fire on slow runs) — PR Dispatch a manual trigger fire instead of running it in the request #679lowtest_flow_starts_after_commit's spawn probe flakes on a 250ms grace under make test — PR Wait on the dispatched task instead of a 250ms grace #705mediumThe sandbox activity log lives only in the service's 200-entry ring buffer, so a restart loses it — PR feat(agents): heartbeat & agent triggers #537lowThe trigger create dialog: a fixed 90vh height, markless template cards, unreadable presets — PR feat(agents): heartbeat & agent triggers #537lowA trigger's Environment picker hides the default's name, so an environment reads as missing — PR feat(agents): heartbeat & agent triggers #537lowThe API trigger is a ghost button in the toolbar, not a tile beside the portals — PR feat(agents): heartbeat & agent triggers #537mediumfrontendAn approval alert cannot reach its request past the first queue page -list_approvalsreturns the 50 oldest and the tab has no pagination, so the newly parked run the email is about is the one row the reader cannot get to (found reviewing fix(approvals): send the parked-run alert to the queue, not the Builder #1202)What a run costs
highCommit the run row so a failed run is still accountedmediumExclude a resumed run's own spend from its budget baselinelowA failed terminal commit replaces the cancellation that ended the runlowThe "could not be priced" caveat misses a tree that straddles the start of the window — PR fix(budgets): count an unpriced tree that straddles the window start #650mediumA message shows a cost with no sign that it is partial, and a conversation shows no total — split out of Add Context manager #49, same point as Improve the Activity page (/runs) — plan + mockup first #45lowPer-message cost is web-chat only, so a channel or API thread totals nothing — found building A message shows a cost with no sign that it is partial, and a conversation shows no total #772Secrets at rest
highMake the vault master key explicit and rotatablehighStop putting tokens in the OAuth redirect URLmediumRequire a freshness claim on an embed tokenmediumVault 'seal N fields under one version' is hand-rolled at 4 models; agent_embed has no key-version columnmediumThe observability secret is validated at run time; the factory docstring claims publish refuses itThe database underneath
highReconcile the delete cascades with the CHECK constraintshighStop holding a pooled DB connection across slow workmediumReap runs a dead process left durably running — the crash-orphan remainder of Stop holding a pooled DB connection across slow work #12's early commitmediumKnowledge singleton's pooled engine can cross worker event loops — pre-existing, found reviewing Stop holding a pooled DB connection across slow work #12mediumDev-stack watchdog kills a healthy reloaded server — found standing up Stop holding a pooled DB connection across slow work #12's smoke stackmediumAdd row locks to four check-then-act racesmediumReturn the five repository-calling routes to the service layerlowA session can be in the sessions page and not in its total: The admin drawer reports "Never signed in" for anyone who has signed out #1256'sopenpredicate evaluates its ownnow()per statement, so a session expiring between the page query and the count query is counted by one and not the otherMiddleware written and never registered
highActually apply the rate limitermediumRegister SecurityHeadersMiddlewareThe product's surfaces and shells
lowModel picker says the org has no models when the profiles read failed — same defect one element from Agent draft can't be published by a builder who can't add a model #591, found reviewing PR fix(builder): warn up front when an agent draft can never get a model #846 — closed; the sibling case is in Six answers to "what models and providers exist", and none of them is the authority #923approvals:decide, ceilinged by the organization, and every waived call still writes its rowmediumChat'stemperatureandthinking_effortoverrides are sent on every turn and read by nothing - the reader left with the general-assistant path inb56ba1feand the popover did not (found scoping A per-conversation approval mode in /chat: follow the agent, approve everything, or ask about everything #925)lowThe chat's Model tab opens empty, so it never says which model the conversation runs on - the published profile is already loaded forcontext_window_tokensand only the window is keptFileCardvariant - the sequel to Chat composer: a long paste should attach, and attachments should look like files #133, under One file viewer, not four: the chat file dialog is the poorest of them #136's one-card constraintlowRating a message spins both thumbs - the spinner is keyed oncurrentRating, which is null on every unrated message, rather than on the button clicked. Same file as rating-buttons hand-rolls two fetch calls instead of a lib client and a hook #563MemberPickerinstead of an email box, room to breathe, and icons on view/editlowDashboard pages have no bottom padding -maindeclarespb-20 lg:pb-16on a flex-column scroll container, which does not paint it once the page overflows; four surfaces have re-added it inside the content at three different valueslowActive sessions paging blanks the card and jumps the scroll - the one paged query with nokeepPreviousData, whichquery-freshness.tsalready diagnoses in writinglowThe run detail panel survives a tab switch on /runs, and belowlgit hides the tab you switched to - the panel is page state, the tab is uncontrolled, so nothing clears the focus. Blocks The approval email links to the agent's Builder page, which has no approvals queue #935mediumThe approval email links to/agents/{id}, which has no approvals queue - there is no URL for the Approvals tab to link to (found scoping The run detail panel survives a tab switch on /runs, and below lg it hides the tab you switched to #934)mediumThe approval alert is mailed to people who may not decide -builderandmemberhold noapprovals:decide, so the initiator the default audience names follows Review the request to a page with no Approvals tab at all (found by the reviewer on PR fix(approvals): send the parked-run alert to the queue, not the Builder #1202)mediumAlert emails name no organization, so they open whichever one the reader last used - all four notification URLs are organization-agnostic whileactiveOrgIddecides what the page acts on;run.organization_idis in scope and discarded. Same class as The members page judges permissions from the active organization while acting on the one in the URL #1032 (found by the reviewer on PR fix(approvals): send the parked-run alert to the queue, not the Builder #1202)Later(found finishing the Running tab in Sandboxes, runtimes and workspaces: an attachment that never reaches the workspace, a CSP that blocks every PDF, and seven things nothing explains #1039; the truncation notice is on that branch)mediumweb_search and create_chart render as raw JSON: the frontend matches tool names the backend stopped emittinglowA new conversation shows no agent until the page is reloaded — the listing is fetched before the first answer existsmediumEvery wrong-method request answers 500 instead of 405: OpenTelemetry's FastAPI instrumentation reads.pathoff_IncludedRouter, which FastAPI 0.141 has none of (found reviewing fix(builder): configure an agent in one place, and stop guessing at what a reader sees #914; pre-existing, reachable on every route)/admin/organizationshas no search, sort, filter or pagination, and the route behind it answers none - the one page Standardise the admin pages (users and tabs) to the shared table and list-page shell #284's chain left with the shell and none of the controls/admin"Overview" earn a page? Six figures and three links that all exist elsewhere - theplatformdashboard widget reads the same/admin/stats, and the three links repeat three of the five tabs above themmediummcp-server-list still uses native confirm(): no busy-guard, so a double-click can double-DELETElowOnboarding steps on the MCP page vanish when the server catalog is empty — PR feat(onboarding): first-run tour, guided flows #624lowA still-parked run says nothing about waiting once the page is reloaded — PR fix(chat): keep a parked run saying it waits after a reload #653lowEvery baked MCP logo is keyed on a brand domain the lookup never asks for — PR fix(mcp): key baked logos on the URL host the lookup asks with #648mediumMCP OAuth callback outcome is read by nothing - no toast after the provider redirect — PR Announce the MCP OAuth outcome after the provider redirect #682mediumRun export drops the on-screen status/surface/rating filters it claims to carry — PR Frontend standardisation: one table, one shell, admin on both, skills without install, map in four directions #759highWeb chat never rehydrates a conversation's history, so a reopened thread runs with no context — found scoping Add Context manager #49Models and providers
PROVIDERS(27),model_listings.json(20),model_fallbacks.json(7, and not about fallbacks), thegenai_pricessnapshot,image_models.jsonand two hand-written tables indocs/models.md, over a run-time authority that is Pydantic AI's own inference. Seven providers answer an empty picker today.Performance and security (audit 2026-08-19)
The sweep behind these is
Audit agenticos 2026-08-19 performance and security. #947 isthe one that is not like the others: it is a live, unauthenticated denial of service.
criticalsecurityRate-limit the auth surface, and take bcrypt off the event loop - 171 ms measured percheckpw, no limit on any route inauth.py, so ~6 req/s saturate a worker. Also an enumeration timing oracle and an unlimited mail amplifier. Supersedes the auth half of Actually apply the rate limiter #7highThe ingestion worker creates and abandons a pooled engine per document -aclose()has one caller in the repo;get_worker_db_contextdoes the opposite deliberately one module awaygather, because Two gated tool calls in one model step race the request's AsyncSession #169EDIT_INTERVAL = 1.0CI, the suite and the tools
lowCode health clean-up: nine small items from the 2026-08-01 auditlowe2e:journey.specflakes reading the hosted link after publish - the same class, in the one spec e2e: sharing and skills specs fail when a mutation's refetch lands stale #154 did not cover; the reload before the read narrows the window rather than closing it - found on PR feat(capabilities): memory an agent keeps, and search over what was said #1383, which touches none of itgen:mcp-logoswrites an unformatted file, somake lintfails until the commit hook rewrites it — found writing Frontend dependencies: date-fns is unused and three icon libraries answer one question #156's generator, which solves it — PR refactor(frontend): draw every brand mark from one generated glyph set #835, alongside Frontend dependencies: date-fns is unused and three icon libraries answer one question #156highThe Resend email provider imports an SDK declared nowhere — found by thedeptrygate Drop three unimported dependencies and gate on the next one #831 adds, and what keeps DEP001 switched off until it is answered — PR Three claims the code could not keep #842; the provider was never reachable and is deleted, so Resend as an email provider, properly, or not at all #843 records what bringing Resend back would costgoogle-api-python-clientand every model-provider extra stay in the image — the two questions #155 deliberately excluded from its own PR — closed as recorded, both answers still holdlowmake auditfails the required Security Scan job on a single pypi.org timeout — one request per locked distribution, no retry; the network half of the same problem #154 records for e2elowyamlfmt, zizmor and the pre-commit basics read only changed files, and no gate reads the treehighThe ai-review reviewer has produced nothing since 2026-08-05, and reports successlowtest-frontendgoes red on a 5stestTimeoutcoverage instrumentation cannot meet — a different spec each run, each passing alone; the vitest half of the timing-flake pair #154 and #162 record for e2e — found reviewing PR #624mediumFrontend coverage gate red on Node 26: the built-insessionStorageshadows jsdom's in vitest, soresume.ts's catch loses coverage while every test passes - the sessionStorage half of the localStorage normalizationvitest.setup.tsalready does; pre-existing from PR #624, found running the gate for #537's trigger-wizard restylelowfrontendlayout.test.tsxreaches the network - the root layout's unmockedreadBranding()makes the spec depend on what is listening on :8000, so it hangs against a container that accepts connections without answering; green on CI and against a healthy backend, which is why nobody had seen itlowciFlaky:test_lifespan_drain.py::test_the_lifespan_waits_for_in_flight_background_workfails under xdist withRuntimeError: Event loop is closedand passes on a plain re-run - the test that guards the drain #1137 and #1274 depend on — root cause is the module-globalmodel_catalog._listing_clientclosed on a dead loop; mitigated in PR #1378 (the drain test stubs the close), the conftest reset that fully fixes it stays open; found working #1364 (duplicate #1381 folded in)enhancementciPublishagenticos-backendandagenticos-frontendto GHCR on everyv*tag (andedgefrommain);docker compose uppulls them, a clone keeps building throughdocker-compose.override.yml, the quickstart needs only Docker. Blocked by #1544. Neighbours #1512 (GHCR package visibility), #1415 (SBOM per release), #143 (thedockerjob leavesci.yml)Documentation
mediumCorrect three documentation claims that describe behaviour that does not existmediumvalidate_webhook_urlclaims DNS-rebinding protection its callers undo by re-resolving — same shape as #29 and #561; the claim is narrowed rather than delivered — PR #842highMCP OAuth discovery URLs are remote-chosen, and the validated address is not pinned (found reviewing PR #842 — #840's "an operator typed it" reasoning covers the connection URL and thecdp_url, but notmcp_oauth._send, whose every hop the remote server names; this is the delivery half #840 declined) — PR #875lowAn MCP discovery URL with a bad port answers 500 rather than the refusal —httpx.InvalidURLis not anhttpx.HTTPError, so the flow's three catches miss it and #872's fix cannot reach it (pre-existing, found mergingmaininto PR #875)mediumA blocked MCP server URL answers a generic 500 instead of naming the refusal —SSRFBlockedErroris aValueError, which no handler maps (pre-existing, found reading the same path) — PR #872mediumImporting a spec YAML answers a generic 500 for every kind of mistake in it — a raw pydanticValidationErroris not aRequestValidationError, so no field error reaches the person who edited the file (found sweeping the same defect in PR #872)lowAn ingestion override with too large achunk_overlapanswers a 500 rather than the form its own docstring says it refuses to (same sweep) — PR #881lowA per-field refusal answers withdetails.errors, which the form cannot mark — the frontend readsdetails.fields, so all four call sites of that idiom show a sentence and highlight nothing (found fixing #874; #873 added the fourth) — PR #892lowThe same defect in a third shape: seventeen refusals name one field with a singulardetails.fieldand the envelope's message, which no form reads either (deliberately left out of #882 — converging one of eighteen would make it disagree with its siblings) — PR #900lowA connector's per-field refusal is flattened to a sentence byvalidate_config's(bool, str)protocol, so the sync-source wizard marks nothing — the one place #891 could not reach (found fixing #891)lowA refused model id is posted back indetails={"model": model}— the caller's own submission, serialized and logged — and still marks no input (found fixing #891)lowdocs/testing.md documents three fixtures and a layout the suite does not haveThe roadmap
web_fetchcapability (SSRF-guarded, ownweb:fetchscope); search-provider spend metering decided against and recorded on the issue, which found #840web_fetchhalf)tool_output_limits; reference refreshed)tool_output_limitsspills at run end on non-run-scoped workspaces (found reviewing #57)system_reminders)memory_files,memory_mem0) and drop the operator console; the native fact store, theorigintrust tier and the Memory tab went with it. #1384, #1385 and #1386 were closed as superseded by the same reshapeAudit clean-up bags
lowBackend duplication & quality cleanup (audit 2026-08-10)mediumuseKBDetail holds server data in useState off React Query; two registered query keys are deadmediumAdmin query-key factories type params as unknown; admin.ratings is keyed three waysmediumThe ratings-by-day shape is declared four times under two confusable namesmediumTwo upload routes serialize the same schema differently (response_model_exclude_none)#562mediumType the dict[str, Any] service contracts in sandbox and connectors - done, 0.0.214lowExtract the runner's duplicated prompt-flatten and terminal-state assemblylowUnify the two backend-schema form renderers (SchemaForm vs ConfigureStep)lowConverge the backend connector config schema onto JSON Schema (dropConnectorConfigField) - the L, higher-risk half #568 deferred; #568 unified the renderer, this unifies the wire shapelowFrontend duplication & quality cleanup (audit 2026-08-10)Bookkeeping
The spine — what blocks what
Ordered by how much it unblocks. Everything not named here is independent.
1. #440 blocks the whole refusal cluster, and it was closed in error.
Reopened today after verifying on
main:PiiRedactionFilteris attached to the rootlogger at
app/core/logging.py:73, where it never sees a record from a modulelogger, and
setup_logginghas exactly one caller —app/main.py:217— so the Prefectworker never installs it at all. Every fix in that cluster works the same way: stop
putting the vendor's text in the response, keep it in the
logger.exceptionbeside theraise. That remedy is only sound if the log is scrubbed, and it is not. #676, #681,
#686, #695, #699 and #659 all inherit the assumption;
app/services/rag/failures.py:19states the failure in the present tense and cites the number.
2. #311 is why the sweeps keep finding things. The automated reviewer has produced
nothing since 2026-08-05 while reporting
success; eleven pull requests merged on asentence that read like a verdict. Until it is fixed the only review before a merge is
a human one. Not a formal blocker on anything — but it is the reason clusters like
"the frontend's own API surface" are found in audits rather than in review.
3. #564 is the structural fix for five symptoms. #13, #30, #546, #553 and #702 are
all the same ~40 hand-rolled
backendFetchhandlers undersrc/app/api/**: oneunencoded and unauthenticated (#13), eighteen unencoded (#30), the family swallowing
the backend's refusal reason (#546), omitting
no-store(#553), and three echoing thebackend's content type (#702). Collapsing them into
platformProxyremoves the class.Decide this before PR #668 merges — it already touches about forty of those files
for #546 alone.
4. #150 → #151 and #47 + #51 → #71, both stated in the blocked issue's own
title. #151 (a global assistant driving the platform through its own API) cannot start
before organization API keys exist; #71 (deep research) needs planning and the web
capabilities first.
5. #139 → #282 → #284. One table primitive, then the list-page shell built on it,
then the admin pages standardised onto both. Doing them in the other order means
writing the admin pages twice. Same shape in #136 → #138, #500: one file viewer
before the files page and the header field it needs.
6. #44 + #213 + #53 → #594. Three feature branches — PR #537 (triggers), PR #587
(dashboard layout), PR #624 (onboarding) — that each land alone and then have to agree
with each other on
main. #594 is that reconciliation and cannot start until all threemerge.
7. #3, #7 and #12 are acceptance criteria of #39, and PR #634 says
Closes #39.#39's "Done when" list is four items: every surface records a run identically (#3),
EmbedSessiontakes a session factory rather than a session (#12), rate limits applyto the public surfaces (#7), and
docs/channels.mddescribes what exists. Three ofthose are separate open issues. Either the footer should be
Refs #39with thecheckboxes ticked as they land, or #39 should be re-scoped to what #634 actually
finishes.
Closesonly when the change genuinely finishes it.Pairs that want one pull request, not two: #7 + #18 (both are one
app.add_middlewareline increate_app), #147 + #148 (adjacent lines of oneaggregation in
repositories/rag_document.py:188-189), #548 → #566 (fix thedisagreement, then collapse the two helpers into one).
8. The connector chain, decided in #938. #984 (a web crawler) is first because it needs no credential and waits on nothing; #985 needs a new
SecretKindfor an Entra app that nothing else wants yet; #988 blocks Azure Blob and GCS and is the condition #938 attached to them - generalizeS3Connectorbefore writing either, not alongside. #990 came before all four and is done (0.0.216), which is what makes a change signal worth naming at all. #982 and #983 are not blockers but every connector on the chain inherits them, so they are worth having before the third one rather than after.The duplicate register — already adjudicated
Recorded so nobody re-litigates them, and so the next filer can see what "the same
bug" has looked like here.
channels/router.py:728, filed three times in eight days. #642 closed 2026-08-12, #636 closed 2026-08-13.mcp-server-list.tsx:360usingwindow.confirm(), filed twice by the same author eleven days apart from two different sweeps. #611's three unique findings copied onto #554 before closing.COMPLETEDwith neither half fixed; both halves re-filed by people who could not find it. #659 is the socket half (PR #675), #676 the run-row half (PR #691). #437 now carries a comment naming them.rag-source-addwriting an unvalidated, unowned collection name.Pairs that look like duplicates and are not — checked, keep both:
instance that is also unauthenticated, which is why it is
severity:highwhere thesweep is
medium. #13 must be in #30's fix, and #30 must not close without it.call site, two fixes, two sets of acceptance criteria.
body says so. #147 landed in #834 rather than alone, because #158 could not
measure its own before/after while the only stored chunk count was a constant 0.
requests (#677, #688).
capabilities, adjacent names.
What has no scope yet
estimated.
W1 · Aug 3–7, amilestone eight days past, and are unactionable as written: "Fix Agent Builder
frontend" names no defect and has no acceptance criterion. #168 was the fifth
empty body and is fixed by this rebuild; these four are not. Either write them or
close them — leaving them is what makes the open count untrustworthy.
plan firstin its title deliberately.Known-stale references
An issue body's cross-references were true when written. About 78 of the open bodies
cite at least one number that has since closed — mostly the roadmap family citing #16
(un-metered embeddings), which is done. The one that changes a decision is #39's
second checkbox, which describes #16 as live; it is not.
Do not chase the rest. Check a referenced number's state before acting on it:
Triage state, 2026-08-13
Every open issue now has a type, a milestone, an
effort:*label and a row on theVstormOSboard. Before today, 84 had no type and 26 — #456 and the whole2026-08-10 audit batch, #545–#569 — were on no project at all, which made them
invisible to every view the board is read through.
Re-deriving this
gh issue list --state open --limit 300 --json number,title,body,labels,milestone gh pr list --state open --limit 100 --json number,title,body # then grep the footersPass
--limit. It defaults to 30 and caps at 100; under a hundred open issues itsilently drops the lowest numbers, which is how #2–#7 once read as closed and a blocker
chain read as already done.
Refs #683, #687.
Filed 2026-08-28, merging the W4 backlog
Thirty pull requests were merged and released as 0.0.316-0.0.343 in one pass. The
five issues above with 13xx numbers came out of that: four from reviewer findings
on branches that were merged anyway, and one flake. Each is in its cluster.
One pull request was not merged: #1128 (#1079, the knowledge retrieval
store crossing worker event loops). Its fix builds a store with its own pool,
which #948 and #12 removed from
main, so a mechanical merge either re-adds thesecond pool or stops fixing the bug. #1079 stays open with the new shape written
down on it.
Filed 2026-08-29, from the MCP redesign
Three came out of one branch (
docs/site-improvements), all in the #64 MCPredesign cluster and all in the same thread of work:
#1339
mediumA Mattermost conversation was one channel, not one thread — the agent read every thread in a channel as one conversation#1340
mediumStarting OAuth from the catalog answered 404#1341
mediumThe Builder picker keyed its rows on the catalog entry, so an organization with several connections to one server could bind only one of them — one card per server with an account select, and per-binding personal substitutionfeatureA member holding two accounts on one service cannot say which an agent speaks as — #1341 declines to guess, and nothing records the choicelowA resumed direct-message run stops speaking as the member who started it —agent_runsrecords the surface but not whether the conversation held one person#1391
featureAn MCP binding is either the organization's account or each person's own — a kind, not a flag. Supersedes #1341's per-binding substitution and #1343'sprivate_to_user; builds on #1342's nomination of a default account. Filed 2026-09-04 — PR #1392mediumPublish and run time each decide MCP prefix collisions, and only publish says solowownAccountStatusre-implements the server's account rule on the client, and they disagree onunauthorizedlowThe personal-service briefing builds a frontend URL in the runner, which a locale-prefixed deployment breakslowAn API run authenticated with a user's own JWT is told nobody is signed inAll four found reviewing PR #1392 before merge; none blocks 0.0.356.
Security programme (milestone
W6 and W7 · Sep 7–18, planned 2026-09-05)The theme for W6 and W7: a client's security review answered in writing, a mechanism and a test behind every row of a controls matrix (HIPAA §164.312, SOC 2 CC6–CC8), an issue behind every gap. #1424 is the epic and the four-area status;
docs/security.md(#1412) is the deliverable the rest feeds.securitymetaSecurity programme - the epicdocumentationsecuritydocs/security.md: threat model, data flows, what is encrypted where, the controls matrixsecurityTraces sent to Logfire carry full prompts and outputs - a content mode onAgentSpec.observability(same card as #561)lowsecurityfrontendCodeQL alert #20 inoauth-return.ts- validate or dismiss with a reason (#135 added the path)cisecurityCodeQL on pull requests, a frontend dependency audit, an SBOM per release (parity via #143)securityfrontendA real Content-Security-Policy; the widget and hosted page are the framing exception (#702 family)cisecurityAsecuritypytest marker naming every refusal test, reported per releasesecurityEncrypted connections to Postgres and Redis, and a doctor linesecurityGeneric OIDC sign-in beside Google; honours the sign-up policy (#914). SAML/SCIM and TOTP are in the roadmap note, unfiledsecurityPer-organization data retention with an audit floorsecurityErasure and export of one person's data - whatUserService.deleteleaves (#941 neighbour)securityAudit trail export and a hash chain (#20, #983 neighbours)securityS3-compatible file storage with server-side encryption beside the local disksecurityA HIPAA deployment profile and adoctor --profile hipaacheck that proves a running deployment matches it - the configuration half of #1412Existing security issues scheduled under the same theme rather than moved here: #943 and #1044 (impersonation),
#913, #1047, #1066, #1245, #555, #1072 and #150. Bartek's 2026-09-05 sweep added #1427 (a deactivated member's
channel turn), #1437 (a WebSocket outliving its session), #1438 (identity binding while impersonating) and #1439
(a password change that revokes nothing) — the same theme, filed independently.
W6 and W7 are one milestone as of 2026-09-05, and everything schedulable was drained out of Later into it.
Seven issues stayed behind: #52 and #56 (themes planned in the roadmap note), #70 (unscoped), #71 and #151
(blocked), #54 (needs an architectural decision for the realtime half), #985 (needs the tenant-consent answer).