FeatureMediumMCP GatewayAPIAction required: No
F11 Path C — BM25 + Reciprocal Rank Fusion hybrid retrieval
search_semantic and gc_search_docs now fuse dense ONNX-embedded scores with classical BM25 lexical scores via Reciprocal Rank Fusion (k=60). Lexical recall improves for exact-token queries where cosine similarity undershoots. Response carries score_type: "rrf" | "cosine" so callers can detect silent fallback to cosine-only mode.
- Fixed in:
- API v1.35.1
- Components:
- token-saver-5000/src/bm25_utils.py — NEW: BM25Okapi-based scoring with file_id IDF guard · token-saver-5000/src/semantic_modulator.py — _bm25_scores_for_nodes + rrf_fuse integration · token-saver-5000/tests/test_f11_bm25_rrf.py — TestIdfPollutionRegression + cosine-byte-identical canary
Show details ▾Hide details ▴
- Zero new Python dependencies — BM25Okapi pattern extracted from existing codebase into src/bm25_utils.py.
- file_id filter applied to BM25 candidate set before scoring — prevents cross-document IDF pollution across unrelated ingested docs.
- Regression-locked by TestIdfPollutionRegression (mutation test killed a global-corpus mutation) + cosine canary.
- Submodule token-saver-5000 PR #15 squash-merged; outer repo pointer advanced.
FeatureMediumMCP GatewayAPIWebAction required: No
MCP catalog hot-reload + circuit-breaker telemetry + SSE keepalive + scoped API keys
Multi-feature umbrella release closing the OpenClaw/Hermes integration plan v2 and shipping the first half of Phase 2 scoped API keys. Operators can now hot-reload the MCP tool catalog without restarting Fly machines (default OFF, rate-limited 1/hr, admin-only). Circuit breakers (Redis, Postgres, Polar, Beehiiv) emit Prometheus telemetry following Resilience4j convention. MCP SSE connections survive idle proxy timeouts via FastMCP PingMiddleware (default OFF, 45s interval). Dashboard ships a least-privilege scope picker for minting scoped gc_ keys. URL fetcher hardened against SSRF (4 new protections). Full Phase 2 still gated as v1.35.0 when Chunks 4 + 6B land.
- Fixed in:
- API v1.34.35
- Components:
- api/app/routers/v1/admin_mcp_reload.py — POST /v1/admin/mcp/reload (admin-only, rate-limited) · api/app/services/mcp_reload_metrics.py — mcp_reload_cache_invalidations_total counter · api/app/services/circuit_breaker_metrics.py — Resilience4j multi-label gauge + transitions/outcomes counters · api/app/services/resilience.py — beehiiv breaker folded into central registry · api/app/mcp_gateway.py — _McpKeepaliveMiddleware (gated by MCP_KEEPALIVE_ENABLED) · api/app/routers/v1/keys_confirm.py — POST /v1/keys/confirm/{token_hash} · api/alembic/versions/0061_confirm_tokens.py — confirm_tokens table · apps/web/src/components/dashboard/keys/ScopePicker.tsx — 8×2 grid + 4 quick-select presets · token-saver-5000/src/url_fetcher.py — IPv4-mapped IPv6 normalization, DNS rebinding, metadata hostname blocking, redirect-to-internal blocking · api/requirements.txt — fastmcp>=2.3.0,<3.0.0 added; mcp>=1.27.0
Show details ▾Hide details ▴
- F1 hot-reload: POST /v1/admin/mcp/reload (admin:write scope, MCP_RELOAD_ENABLED=0 default, ≤1/hr rate-limit via Upstash NX lock, transactional COW swap leaves catalog intact on any build exception)
- F2 telemetry: circuit_breaker_state{name, state}, circuit_breaker_state_transitions_total{name, from_state, to_state}, circuit_breaker_calls_total{name, outcome} on /metrics
- F3 keepalive: FastMCP PingMiddleware default OFF (MCP_KEEPALIVE_ENABLED=1 to enable, 45s interval default — under AWS ALB 60s + Cloudflare 100s)
- Phase 2 Chunk 2: every /v1/* route now carries Depends(requires_scope("<resource>:<verb>")); 50 router files decorated; AST coverage gate locks it
- Phase 2 Chunk 3: confirm_tokens REST endpoint for rotate/revoke/scope-grant workflows (Clerk JWT auth, single-use, 15-min TTL, SELECT FOR UPDATE atomic consume)
- Phase 2 Chunk 5 (web): ScopePicker UI for minting least-privilege scoped gc_ keys; legacy keys still default to god-mode
- Phase 2 Chunk 6A: SSRF hardening on ingest_context.file_url path; closes 4 attack vectors
- Bonus: fastmcp standalone now actually installed (was silently no-op since v0.5.0); EventStore-backed session resumability now functional
FeatureLowMCP GatewayAction required: No
MCP tools now signal parallel-dispatch eligibility via readOnlyHint annotation
Three read-only MCP tools (gc_lookup, search_semantic, read_skeleton) now stamp ToolAnnotations(readOnlyHint=True) in their schema. Compatible MCP clients (Claude Code via isConcurrencySafe(), others honoring the MCP spec annotation) gate parallel tool dispatch on this flag. Forward-compatible the moment client behavior shifts toward higher parallelism rates — near-term value is muted because Claude Opus 4.6 empirically emits ~1 tool per response in sustained testing, but cost-to-ship was 15 minutes for an annotation patch.
- Fixed in:
- API v1.34.34
- Components:
- api/app/mcp_gateway.py — _MCPToolAnnotations import + gc_lookup annotation · token-saver-5000/src/handlers/mcp_core.py — annotations on read_skeleton + search_semantic · api/tests/test_readonly_hint_annotations.py — 2 regression tests (live introspection + AST scan)
Show details ▾Hide details ▴
- gc_lookup: annotated readOnlyHint=true
- search_semantic: annotated readOnlyHint=true
- read_skeleton: annotated readOnlyHint=true
- No version bump on the MCP spec — uses the existing ToolAnnotations spec field
- F4 batch dispatcher was DROPPED (6/6 council consensus) — quota-bypass risk + Opus empirically emits ~1 tool/response
Bug fixMediumMCP GatewayAction required: No
gc_search_docs now matches morphological variants (authentication → authenticate)
Third hotfix in the Phase 1 docs-MCP ship train. v1.34.32 fixed the live-URL fallback and the character cap, but live dogfood confirmed gc_search_docs("authentication") still returned empty results because the corpus has "authenticate" / "auth" / "auth-gated" but no exact "authentication". Codex's BM25 matched tokens exactly. Now query terms ≥6 chars match via 6-char prefix, so "authentication" matches "authenticate", "compression" matches "compressed", etc. Short queries (<6 chars) still require exact match to avoid false positives.
- Fixed in:
- API v1.34.33
- Components:
- token-saver-5000/src/handlers/docs_handlers.py — _term_freq_with_stemming() with 6-char prefix rule · token-saver-5000/tests/test_docs_handlers.py — 4 new regression locks (13/13 total)
Show details ▾Hide details ▴
- Prefix-match rule: query terms ≥6 chars match doc tokens sharing first 6 chars
- Short queries (<6 chars) require exact match — avoids false positives on common words
- "authentication" → matches "authenticate", "authenticated", "authenticating"
- "compress" → matches "compressed", "compression" (acceptable on docs surface)
- "auth" (4 chars) → exact match only
- Lesson: search-quality bugs only surface via live realistic-phrasing queries — encoded for Phase 2/3 specs
Bug fixHighMCP GatewayAction required: No
Hotfix: gc_search_docs now actually returns results; gc_read_doc respects size limits
Live verification of v1.34.31 immediately caught 2 ship-blocking bugs. (1) gc_search_docs returned empty results because the spec assumed a static llms.txt path that does not exist — production /llms.txt is a Next.js dynamic route. Fixed by falling back to a live URL fetch at module load. (2) gc_read_doc returned 80KB markdown blobs that exceeded orchestrator tool-result limits because token-level truncation cannot help when the content is HTML-as-markdown with very long lines. Fixed with a defensive 20K-character cap after token-level truncation. Both bugs caught within 5 minutes of the v1.34.31 deploy via dogfood verification.
- Fixed in:
- API v1.34.32
- Components:
- token-saver-5000/src/handlers/docs_handlers.py — _docs_root + _read_llms_txt fallback chain + MAX_DOC_CHARS cap · token-saver-5000/tests/test_docs_handlers.py — 4 new regression locks (9/9 total)
Show details ▾Hide details ▴
- Live-URL fallback: GOTCONTEXT_LLMS_TXT_URL env override + 5s urllib timeout + empty-string-on-failure
- Character cap: MAX_DOC_CHARS = 20000 with friendly truncation notice
- 4 new regression tests cover both fix paths + defensive defaults
- Verification: dogfood-driven — bugs caught by live MCP call within 5 min of v1.34.31 ship
- Lesson: spec path-assumptions must be verified pre-dispatch; encoded for Phase 2/3
FeatureLowMCP GatewayAction required: No
Search + read gotcontext docs via MCP (free tier) — gc_search_docs + gc_read_doc
First ship of the MCP API parity plan (3-4 week roadmap, CEO-approved). Agents can now search and read gotcontext product documentation without leaving their terminal. Free for all plans, no auth required — top-of-funnel conversion utility per industry research on Context7 + ref.tools precedents. Install the plugin once, ask your AI "how do I authenticate with gotcontext?", and it answers from the live docs.
- Fixed in:
- API v1.34.31
- Components:
- token-saver-5000/src/handlers/docs_handlers.py — 2 new handlers with BM25 ranker over llms.txt · token-saver-5000/src/handlers/mcp_core.py — gc_search_docs + gc_read_doc tool registrations · api/app/services/plan_gating.py — both tools added to _FREE_TOOLS allowlist · plugins/gotcontext/skills/quick-start/SKILL.md — new plugin skill documenting both tools
Show details ▾Hide details ▴
- 2 tools (not 1) — separates name disambiguation from content retrieval (Context7 + ref.tools pattern)
- BM25 over heading-line chunks parsed from public llms.txt (k1=1.5, b=0.75 standard params)
- Session-aware deduplication via session_id — repeated calls in same session return new URLs
- Truncates >5000-token responses to a similarity-around-anchor excerpt with truncated:true flag
- Module-load cache; Fly redeploy is the invalidation boundary (no Redis TTL needed)
- Dispatch chain: codex gpt-5.5 wrote → opus reviewed (4/5 APPROVED + 1 BLOCKER fixed) → orchestrator shipped
- Tests: 5/5 new + 105/105 regression sweep + 11/11 plugin parity
ImprovementLowMCP GatewayAction required: No
modulate_region now accepts singular node_id alongside node_ids
Dogfood finding F10 from 2026-05-23: agents calling modulate_region(node_id="x") — the intuitive singular call — previously got a validation error because only node_ids (plural list) was accepted. Now both forms work: singular node_id (string) wraps to [node_id]; canonical node_ids (list) unchanged. Customer impact: agents using the singular intuitive call succeed on first try instead of needing to learn the array form. Backward compatible — existing node_ids callers unchanged.
- Fixed in:
- API v1.34.30
- Components:
- token-saver-5000/src/handlers/mcp_core.py:270 — schema accepts node_id alongside node_ids via anyOf · token-saver-5000/src/handlers/compression_handlers.py:1079 — handler normalizes singular to list, raises clear ValueError if neither is passed
Show details ▾Hide details ▴
- Schema-additive: anyOf enforces one of (node_id, node_ids) is present
- Handler precedence: node_ids (canonical) wins if both are passed
- Clear ValueError with [TIP] when neither is provided
- 4 regression tests lock the new behavior + the canonical path
- Backward compatible — no breaking change for existing node_ids callers
ImprovementLowMCP GatewayAction required: No
batch_ingest_documents MCP schema now exposes scope params (session_id, workspace_id, user_id, agent_id)
Discoverability fix from v1.34.28 F12 verification: the handler already accepted session_id but the MCP tools/list schema for batch_ingest_documents did not declare it (or the other 3 scope params). Agents enumerating the catalog now see these params via the documented schema. Per-session savings tracking from v1.34.28 now works correctly when callers want non-default session scoping. Handler behavior unchanged.
- Fixed in:
- API v1.34.29
- Components:
- token-saver-5000/src/handlers/mcp_core.py:1083 — added **SCOPE_PROPERTIES to batch_ingest_documents inputSchema (matches the pattern already on ingest_directory)
Show details ▾Hide details ▴
- Schema-discoverability fix; handler behavior unchanged
- Agents using batch_ingest_documents can now pass workspace_id / user_id / agent_id / session_id via the documented schema
- Lined up with v1.34.28 F12 class-completion (per-session savings tracking on bulk paths)
- Schema-stability tests (tool ordering + determinism + hash) green post-change
Bug fixMediumMCP GatewayAction required: No
Per-session savings now reflect read_skeleton, batch_ingest, ingest_directory, and filter_cli_output
v1.34.27 fixed F12 narrowly on ingest_context, but handlers that bypassed it (BatchCompressionManager.compress_batch → compressor.ingest_file_async directly) still skipped the per-session SavingsTracker. v1.34.28 wires the four remaining real-savings producers: read_skeleton, batch_ingest_documents, ingest_directory, and filter_cli_output. The broader F12 class is now fully closed — every agent that calls get_savings_report / get_savings_inline mid-session sees real numbers regardless of which compression tool produced them.
- Fixed in:
- API v1.34.28
- Components:
- token-saver-5000/src/handlers/compression_handlers.py — handle_read_skeleton + handle_batch_ingest + handle_ingest_directory now call _get_tracker().record() after compression · token-saver-5000/src/handlers/token_optimization_handlers.py — handle_filter_cli_output wires the tracker; tokens estimated via len()//4 (matches TokenEstimator fast method) · token-saver-5000/tests/test_compression_handlers.py — TestHandleReadSkeletonF12ClassCompletion (2 tests) regression-locks read_skeleton path
Show details ▾Hide details ▴
- Same lazy-import + swallow-failure pattern as v1.34.27 (tracker breakage never fails the underlying tool)
- read_skeleton: re-compression delta on every call (skeleton.total_tokens vs skeleton_tokens)
- batch_ingest_documents + ingest_directory: one tracker event per successful result
- filter_cli_output: token-savings estimate via len()//4 fast method (exact bills still route through /v1/usage/by-model)
- Class-completion of F12 — pre-fix any tool other than ingest_context produced silent zeros in get_savings_report
- Skipped (intentionally): modulate_region (expands), search_semantic (no delta), compress_codebase (enumerates files), diff_reingest (different metric shape)
Bug fixMediumMCP GatewayAction required: No
Per-session savings reports now reflect ingest_context activity
Pre-fix the SavingsTracker was dead infrastructure — _get_tracker existed but was only ever called by .get_report() paths, never .record(). Every agent that called get_savings_report or get_savings_inline mid-session after an ingest_context call saw $0 / 0 tokens saved even after real compression activity. The mismatch with /v1/global-savings (which already showed 1.38M tokens saved across all users from the persistent usage_events table) confirmed the per-session tracker was never receiving the events the report was reading. Agents querying their own savings now see real numbers.
- Fixed in:
- API v1.34.27
- Components:
- token-saver-5000/src/handlers/compression_handlers.py — handle_ingest now calls _get_tracker(session_id, model).record() after the cost_savings block · token-saver-5000/tests/test_compression_handlers.py — TestHandleIngestF12SavingsTrackerWired (2 tests) regression-locks both the positive path and the defensive failure-shield
Show details ▾Hide details ▴
- Lazy-import of _get_tracker avoids module-load circularity
- Tracker failures are swallowed via logger.warning — they MUST NOT fail the ingest call
- Removed obsolete test_with_query_returns_both_stats_and_query_skeleton (mocked final_skeleton as a string, which is exactly the F7 bug shape that landed in da74691)
- Replaced by test_with_query_pipeline_returns_skeleton_response_object_serializes_cleanly — uses a real SkeletonResponse dataclass
- 14/14 ingest tests pass (was 12/12 + 1 broken-after-F7 + 0 F12)
InternalLowAPIAction required: No
Unregister /webhooks/resend route — no Resend webhook configured on account
CEO confirmed 2026-05-23 that Resend has no webhooks configured, so the 35 Sentry events on GOTCONTEXT-API-8 over 21h were noise (bot probing / stale pings), not real Resend retries. POST /webhooks/resend now returns 404 (route removed), Sentry GOTCONTEXT-API-8 stops firing entirely, public attack surface shrinks. Handler preserved in tree for when we wire bounce-handling for real.
- Fixed in:
- API v1.34.26
- Components:
- api/app/main.py — removed resend_webhook import + include_router; RESEND_WEBHOOK_SECRET removed from _REQUIRED_WEBHOOK_SECRETS · api/tests/test_webhook_secret_validation.py — assertion updated to confirm RESEND_WEBHOOK_SECRET is NOT in the required tuple
Show details ▾Hide details ▴
- POST /webhooks/resend now returns 404 (route unregistered)
- Sentry GOTCONTEXT-API-8 stops firing entirely (not just suppressed at WARN per v1.34.24)
- Handler module api/app/webhooks/resend.py preserved (dormant) for future bounce-handling
- Re-enable steps documented in api/app/main.py inline comment
FeatureLowMCP GatewayAction required: No
Agent-driven gc_ key lifecycle: mint + revoke via MCP
Lets agents (CI runners, Claude Code sessions, automation) self-serve gc_ API key lifecycle without a dashboard session. Pro+ only. Closes CEO directive 2026-05-23 "we should add mcp to make api and revoke it for simplicity" — surfaced during dogfood when chrome-extension OAuth dance failed mid-session.
- Fixed in:
- API v1.34.25
- Components:
- api/app/mcp_gateway.py — gc_mint_api_key + gc_revoke_api_key tool defs + dispatch handlers · api/app/services/plan_gating.py — both tools added to _PRO_TOOLS allowlist
Show details ▾Hide details ▴
- gc_mint_api_key: name (required), project_id (optional), expires_in_days (1-365, default 30), environment (prod|staging|dev), default_model — returns full key value ONCE
- gc_revoke_api_key: key_id (required) — owner-only guardrail, cross-user returns 403
- Both reuse existing _pg_insert_key / _pg_revoke_key / _redis_write_key / invalidate_api_key_cache helpers — same code path as REST POST /v1/keys + DELETE /v1/keys/{key_id}
- Audit log entries api_key.created / api_key.revoked with via=mcp_tool metadata
InternalLowAPIAction required: No
Downgrade Resend webhook missing-secret log from ERROR to WARNING
Sentry GOTCONTEXT-API-8 was hitting 31 events / 21h pre-fix. The 400 response is unchanged (Resend honors it and stops retrying), only the noise floor drops. Real fix requires setting RESEND_WEBHOOK_SECRET via flyctl; this commit stops fighting Sentry while that waits.
- Fixed in:
- API v1.34.24
- Components:
- api/app/webhooks/resend.py — logger.error → logger.warning when RESEND_WEBHOOK_SECRET is unset
Show details ▾Hide details ▴
- Sentry default event_level=ERROR captures one event per inbound webhook
- WARNING level skipped by Sentry without losing real signature-mismatch errors
- 400 response unchanged — Resend stops retrying as before
Bug fixHighMCP GatewayAction required: No
read_skeleton JSON serialization 500 + ingest_context ENOENT on multi-segment file_id
Two CRITICAL MCP bugs caught by Customer #1 (us) dogfooding the gotcontext-prod MCP per CEO directive. F7 (Sentry GOTCONTEXT-API-H): every handle_ingest call with the new inline query= parameter returned 500 because the response embedded a raw SkeletonResponse dataclass that json.dumps cannot serialize. F8 (Sentry GOTCONTEXT-API-G): every ingest_context with a multi-segment file_id (the canonical pattern from CLAUDE.md, e.g. docs/audits/foo/bar.md) hit ENOENT on first write because parent dirs were not pre-created under .semantic_modulator_data/documents/. Both fixes land in the token-saver-5000 submodule pointer bump.
- Fixed in:
- API v1.34.23
- Components:
- token-saver-5000/src/handlers/compression_handlers.py — handle_ingest projects pipeline final_skeleton to scalar fields before embedding in response · token-saver-5000/src/persistence.py — _atomic_write_json and _atomic_write_npz pre-create parent dirs via Path.parent.mkdir(parents=True, exist_ok=True)
Show details ▾Hide details ▴
- F7: every ingest_context with query= returned 500 — broke recommended ingest+query workflow
- F8: every multi-segment file_id like docs/audits/foo/bar.md ENOENT on first write
- Both bugs caught by 1 session of MCP usage via Sentry MCP search at hourly self-check
- CI smoke-test of canonical workflow path (estimate_tokens → gc_pre_flight → ingest_context → read_skeleton with query=) was missing and would have caught F7 at deploy time
Bug fixLowMCP GatewayAction required: No
Token-saver test suite parity: default selection_mode="auto" + 4 test fixes
Fixes four failing token-saver-5000 tests surfaced by dogfood findings F3 and F4. Default selection_mode for handle_read_skeleton changed from "baseline" to "auto" (routes to the best available strategy). Three additional test-parity fixes: compression_ratio assertion corrected for highly repetitive docs, bullet_id assertion flipped to match intentional re-injection, and _encode_with_fallback test patched to cover the real fallback chain. All 3609 submodule tests pass.
- Fixed in:
- API v1.34.22
- Components:
- token-saver-5000/tests/test_compression_handlers.py — test_successful_skeleton_read: selection_mode assert updated to "auto" · token-saver-5000/tests/test_adaptive_compression.py — test_auto_ratio_adapts_to_large_doc: assertion corrected to compression_ratio >= 0 · token-saver-5000/tests/test_prompt_caching.py — test_filter_serialize_excludes_volatile: bullet_id assert flipped (intentionally re-injected) · token-saver-5000/tests/test_coverage_boost3.py — test_encode_unknown_tier_raises: patched full fallback chain via all three _encode_* methods
Show details ▾Hide details ▴
- handle_read_skeleton selection_mode now defaults to "auto" (was "baseline")
- Highly repetitive docs can produce skeletons larger than the deduped node set — this is correct; assertion relaxed to >= 0
- bullet_id is intentionally re-injected after to_display_dict() for MCP usability (ace_refine_context callers need it)
- _encode_with_fallback fallback chain properly covered: patches all three _encode_* methods + ONNX_AVAILABLE / TFIDF_AVAILABLE flags
FeatureMediumAPIMCP GatewayAction required: No
ingest_context file_url — SSRF-hardened remote document fetching for MCP agents
MCP agents can now pass a remote HTTPS URL to ingest_context instead of pasting document text inline. The server fetches and ingests the content server-side with six SSRF mitigations: HTTPS-only scheme, public-IP-only DNS pre-check (blocks RFC 1918 / loopback / link-local / IPv6-private), no-redirect policy, 10 MB size cap, 10 s connect / 30 s read timeout, and a content-type allowlist (text/*, application/json, application/xml, application/yaml). text and file_url are mutually exclusive; successful fetches stamp source_url on the response.
- Fixed in:
- API v1.34.21
- Components:
- token-saver-5000/src/url_fetcher.py — new module; 6-mitigation SSRF-hardened HTTP fetch · token-saver-5000/src/handlers/compression_handlers.py — file_url param wired into handle_ingest_context · token-saver-5000/tests/test_url_fetcher.py — 21 unit tests (happy-path + all 6 rejection classes) · token-saver-5000/tests/test_compression_handlers.py — 3 new handler-level tests
Show details ▾Hide details ▴
- file_url accepts any public HTTPS URL; private/internal IPs rejected with descriptive error
- DNS pre-check resolves the hostname and validates all returned addresses before connecting
- No 3xx redirects followed — prevents redirect-chain SSRF bypass
- 10 MB cap enforced via Content-Length header + streaming byte counter
- Content-Type allowlist rejects binary/media responses before body is read
- source_url field stamped on ingest response for provenance tracking
- text and file_url mutually exclusive — passing both returns a clear validation error
FeatureMediumAPIMCP GatewayAction required: No
gc_rebind_api_key MCP tool — headless project attribution rebind (F7 + F8)
CI runners, Claude Code sessions, and automation can now self-manage key→project attribution without a dashboard session. The new gc_rebind_api_key Pro+ MCP tool rebinds a gc_ API key to a different project (or unbinds it) with an owner-only guardrail, cache invalidation, and an audit trail. The companion F8 fix wires cache invalidation to the existing PATCH /v1/keys/{id} endpoint so attribution is active immediately after a rebind (no 5-min stale window).
- Fixed in:
- API v1.34.20
- Components:
- api/app/mcp_gateway.py — _gc_rebind_api_key_tool definition + dispatch handler · api/app/services/plan_gating.py — gc_rebind_api_key added to _PRO_TOOLS · api/app/services/cache.py — invalidate_api_key_cache() helper + UpstashRedisCache.delete() · api/app/routers/v1/keys.py — cache_invalidated field on KeyListItem + PATCH handler integration · api/tests/test_mcp_rebind_api_key.py — 30 new TDD tests (a–i coverage)
Show details ▾Hide details ▴
- gc_rebind_api_key: Pro/Team/Enterprise only; Free plan receives upgrade prompt
- Owner-only guardrail: cross-user rebinds rejected before any DB write
- Revoked keys cannot be rebound
- Cache invalidation evicts both meta blob and auth index from Upstash Redis
- Best-effort: Redis failure does not fail the primary rebind operation
- Audit-logged via api_key.project_rebound action with "via": "mcp_tool" metadata
- PATCH /v1/keys/{id} response now echoes cache_invalidated: true after project rebind
Bug fixMediumAPIAction required: No
News drafter body substance floor — closes auto-published shitpost gap
The full-site audit caught an auto-published one-word "Bruh" article on /news with full Industry News card treatment. The ingest quality gate (v1.34.0) filters by title regex + ups + comments threshold, but the drafter validator accepted any truthy body — a clickbait-titled high-ups Reddit post with a one-word body cleared both ingest AND drafting. The fix adds a minimum body floor at validation time so future shitposts are rejected before queue insertion.
- Fixed in:
- API v1.34.19
- Components:
- api/app/services/news_drafter.py — _validate_draft body substance floor (>=200 chars AND >=30 words) · api/tests/test_news_drafter.py — 2 new TDD tests for the 1-word case + short-body case
Show details ▾Hide details ▴
- body_md must be at least 200 chars (catches "Bruh" trivially)
- body_md must have at least 30 words (catches "lol nice" / "this." / equivalent)
- Prompt template still asks for 600-900 words; floor is a fraction so legitimate short Haiku output passes
- 50/50 news_drafter tests pass post-fix
Bug fixHighWebAction required: No
/news/[slug] Clerk middleware crash + /news/editorial-standards redirect
Every uncached SSR render of /news/[slug] threw `Clerk: auth() was called but Clerk can't detect usage of clerkMiddleware()` because src/proxy.ts only wraps clerkMiddleware around protected routes — the (unauth)/news/[slug] paywall added in v1.28.3 called auth() from a route NOT covered by the matcher. Vercel CDN cache masked most events; Sentry surfaced 2 events from manual URL-entry to /news/editorial-standards. The fix uses try-catch so the throw is treated as "no session" (paywall preview path). Also added a permanent redirect /news/editorial-standards → /news/about so external references no longer hit the [slug] catch-all.
- Fixed in:
- Web v1.29.2
- Components:
- apps/web/src/app/[locale]/(unauth)/news/[slug]/page.tsx — try-catch around auth() · apps/web/src/app/[locale]/(unauth)/news/editorial-standards/page.tsx — new redirect route
Show details ▾Hide details ▴
- Sentry GOTCONTEXT-WEB-C resolved-in-next-release
- External references to /news/editorial-standards now 307-redirect to /news/about
- New feedback memory codifies the Clerk-middleware-matcher gotcha for future paywall additions
ImprovementInformationalAPIAction required: No
News ingest hardening — informal-tone gate + source_slug propagation
Tightened the ingest quality gate to reject overly-informal Reddit titles before drafting, and threaded the source_slug field through the publish path so per-source filters on /news work correctly. Internal-quality improvements — no customer-facing UX change.
- Fixed in:
- API v1.34.18
- Components:
- api/app/routers/v1/cron.py — informal-tone regex addition · api/app/services/news_drafter.py + news_service.py — source_slug threading
Show details ▾Hide details ▴
- See CHANGELOG.md [1.34.18] for the per-commit detail
InternalInformationalAPIAction required: No
News ingest test hardening (internal)
Internal test-suite hardening on the news ingest path — no customer-facing change. Backfilled to satisfy the public-changelog-sync gate (CEO directive 2026-05-19 requires every CHANGELOG.md version to appear on this page).
- Fixed in:
- API v1.34.2
- Components:
- api/tests/test_news_ingest_quality_gate.py
Show details ▾Hide details ▴
- See CHANGELOG.md [1.34.2] for the per-commit detail
Bug fixInformationalWebAction required: No
/news round 1 of rate-fix-rate loop — 2 high-confidence fixes (head metadata + inline code)
CEO directive: "ask thinktank to rate the page 1-10, fix it, re-rate until 10/10." Round 1 council (codex+gemini+droid-opus) returned 7+8+6 = avg 7.0/10. Two unique high-signal findings from droid_opus that the other seats missed: (1) the page <head> metadata still ships the pre-v1.27.6 strings ("AI News" + "Hand-curated") even though the H1 was renamed — that lie shows in browser tabs, Google snippets, Slack unfurl previews BEFORE the page renders. (2) react-markdown v9 stopped passing `inline` prop to the `code` renderer, so every inline `gpt-5.4-mini` on the Cost-of-Inference article rendered as a full-width block mid-paragraph. Also flipped NEWS_INGEST_LAB_BLOGS_ENABLED=1 on Fly (3-of-3 council consensus item — activates OpenAI/DeepMind/HuggingFace ingest from migration 0055 seeds).
- Fixed in:
- Web v1.27.9
- Components:
- apps/web/src/app/[locale]/(unauth)/news/page.tsx — `metadata` export · apps/web/src/app/[locale]/(unauth)/news/[slug]/NewsArticleClient.tsx — code renderer
Show details ▾Hide details ▴
- document.title: "AI News | gotcontext.ai" → "Intelligence | gotcontext.ai"
- meta description: dropped "Hand-curated snapshot…" rewrote with truthful framing matching page subhead
- keywords: dropped generic "AI news" terms, focused on differentiators (AI inference cost, LLM unit economics, MCP gateway)
- react-markdown v9 code renderer: detect fenced blocks via className.startsWith("language-") since v9 no longer passes the inline prop
- Inline `model-name` renders as inline gcCyan badge; fenced ```python blocks still render as full-width block with border (handled by pre wrapper)
- tsc --noEmit clean.
Bug fixInformationalWebAction required: No
RSS feed at /news/feed.xml — aligned with the news strategy ship
The Atom/RSS feed at /news/feed.xml predates the news strategy ship and carried 3 misalignments: (1) channel title "AI News" collided with competitor AINEWS brand (2) channel description still claimed "Hand-curated" — same lie the listing page subhead fixed in v1.27.6 (3) entry links pointed to external source_url instead of on-domain /news/[slug] — the same SEO/attention-leakage problem the listing card title fix addressed. RSS subscribers (power users + LLM agents) now land on our analysis where the source is then linked at the bottom.
- Fixed in:
- Web v1.27.8
- Components:
- apps/web/src/app/news/feed.xml/route.ts — FEED_TITLE, FEED_DESCRIPTION, articleToRssItem link source
Show details ▾Hide details ▴
- Channel title: "gotcontext.ai — AI News" → "gotcontext.ai — Intelligence" (matches H1 rename in v1.27.6).
- Channel description: dropped "Hand-curated" claim; replaced with truthful framing matching the listing page.
- Entry link: always /news/[slug] (on-domain), never external source_url. GUID always isPermaLink="true".
- No backend change. Atom/RSS validation: well-formed XML (no schema change).
- tsc --noEmit clean.
FeatureInformationalAPIAction required: No
Multi-source ingest dispatcher — infrastructure for lab blogs + HN (flag-off)
Days 16-25 of the news pipeline strategy — council pick (claude seat): "lab blogs first" as primary-source upgrade to escape the Reddit-only credibility gap. v1.34.6 ships the dispatcher infrastructure: news_sources.kind field now routes to per-source adapters; quality gate honors source_kind to bypass ups/comments thresholds for primary sources (lab blogs ARE the announcement, no community endorsement signal exists). Flag-off by default — production stays Reddit-only until ops adds source rows + flips NEWS_INGEST_LAB_BLOGS_ENABLED=1.
- Fixed in:
- API v1.34.6
- Components:
- api/app/routers/v1/cron.py — _fetch_source dispatcher + _fetch_lab_blog_rss adapter + per-kind gate bypass · api/tests/test_news_ingest_quality_gate.py — 7 new tests for dispatcher + bypass
Show details ▾Hide details ▴
- _fetch_source(source_kind, url) routes to the right adapter — backwards compatible (unknown kind → reddit-json path)
- _fetch_lab_blog_rss adapter — generic feedparser, items get ups=0/comments=0 (kind-aware bypass means they still pass gate)
- NEWS_INGEST_BYPASS_GATE_KINDS env (default lab_blog_rss|hn_rss) — pipe-separated list of source kinds that skip ups/comments thresholds; title regex still applies
- NEWS_INGEST_LAB_BLOGS_ENABLED feature flag (default 0) — when off, cron query only pulls Reddit-kind rows
- Pre-verified RSS URLs: OpenAI (200), DeepMind (200), HuggingFace (200), HackerNews (200). Anthropic news has no public RSS at the expected path — deferred
- No source rows added — that's a data migration requiring CEO greenlight on which feeds to include and publishing aggressiveness
- 39 tests green (32 existing + 7 new). black + ruff full-tree clean
Bug fixInformationalWebAction required: No
Markdown tables now render on /news/[slug] — caught when first Cost-of-Inference article shipped
The article renderer at /news/[slug] used react-markdown without the remark-gfm plugin, so GitHub Flavored Markdown features (tables, strikethrough, task lists, autolinks) rendered as plain text instead of HTML. Caught the moment the first Cost-of-Inference article shipped (v1.34.5) — the 12-model cost tables that ARE the entire point of the column rendered as `| Model | Provider | …` text. Fix: added remarkPlugins={[remarkGfm]} + table/thead/tbody/tr/th/td renderer components matching the dashboard data-table pattern (border-gcRule frame, monospace numerics, right-aligned figures).
- Fixed in:
- Web v1.27.7
- Components:
- apps/web/package.json — added remark-gfm ^4.0.1 dependency · apps/web/src/app/[locale]/(unauth)/news/[slug]/NewsArticleClient.tsx — remarkPlugins + table styling
Show details ▾Hide details ▴
- Tables wrap in overflow-x-auto so cost-per-day columns don't break narrow viewports.
- th uses uppercase tracking-wider gcMuted (data-table header convention).
- td uses font-mono text-[12px] (numeric alignment matches dashboard).
- No backend change. Article was already rendering correctly via the API.
- tsc --noEmit clean.
FeatureInformationalAPIAction required: No
Cost-of-Inference weekly column — the moat the council called out
Days 11-15 of the news pipeline strategy (per 8-seat thinktank synthesis). Deterministic per-model cost calculations across the live /v1/models pricing catalog (11 models), rendered as a weekly news article. Unlike the drafter pipeline that rewrites Reddit posts, the numbers are NOT LLM-generated — we own the math, no hallucination risk on the cost figures. Article structure (cost tables + framing prose + methodology) is the editorial commitment readers can rely on weekly. NOT scheduled yet — manual workflow_dispatch for the first article so output can be reviewed before committing to weekly cadence.
- Fixed in:
- API v1.34.5
- Components:
- api/app/services/news_cost_of_inference.py (new) — service module · api/app/routers/v1/cron.py — /v1/cron/news-cost-of-inference-weekly endpoint · api/app/middleware/auth.py — _AUTH_BYPASS_PATHS entry · .github/workflows/cron.yml — workflow_dispatch choice list (no schedule yet) · scripts/_coverage_spec.py — endpoint inventory entry · api/tests/test_news_cost_of_inference.py (new) — 10 regression tests
Show details ▾Hide details ▴
- 3 canonical workloads: RAG pipeline (8k/500/1000 q/day) + Long-context coding assistant (32k/2k/200 q/day) + Agent loop (4k/4k × 50 steps × 20 tasks/day).
- Cost table sorts cheapest-first; columns: Model | Provider | Cost / query | Cost / day | Cost / year.
- Prose surrounding tables is templated strings (NOT Haiku-generated) so cost numbers can never be wrong.
- Slug is week-deterministic (cost-of-inference-YYYY-MM-DD) — UNIQUE constraint blocks accidental double-publishing.
- author_clerk_id="system:cost-of-inference" as audit-trail sentinel.
- W14 cascade lock honored: cron handler + auth bypass + workflow_dispatch choice in same commit. Both parity tests green.
- 10 regression tests lock: deterministic cost formula, sort order, title/excerpt length limits, 3-workload constancy, methodology footnote.
FeatureInformationalAPIAction required: No
Drafter writes deeper articles — 600-900 word structured analysis (8-seat thinktank revised sequencing)
8-seat thinktank re-dispatched with the updated use-thinktank skill (v2: PowerShell wrappers + codex-headless wrapper that disables superpowers junction). 7-of-7 returning seats confirmed strategic direction A+D; 3 NUANCE votes unanimous on a sequencing redirect — ship 800-word drafter + on-domain CTA FIRST (smallest highest-impact), THEN Cost-of-Inference moat column tied to /benchmarks data, THEN multi-source ingest (lab blogs / HN / arXiv). v1.34.4 ships the first piece of the revised sequencing.
- Fixed in:
- API v1.34.4
- Components:
- api/app/services/news_drafter.py — DRAFTER_SYSTEM_PROMPT body length + 5-part structure · docs/strategy/2026-05-21-news-pipeline-strategy.md — 8-seat synthesis appended
Show details ▾Hide details ▴
- Body bumped 400-900 → 600-900 words with required 5-part structure: LEDE (SVO factual, no framing) + STATISTICS (verbatim numeric from source) + ANALYSIS (2-3 paragraphs on cost/infra/agent implication) + FORWARD-LOOKING (next concrete thing to watch) + SOURCES (inline on claim, not dumped).
- Title tightened to news-lede or analytical-lead format — banned benefit statements, intriguing hooks, questions.
- Cost: ~$20/mo more (Haiku at 600-900w × 30/day). Within $150/mo cap. Total news spend trajectory under $40/mo after combining with v1.34.3 image-library top-up throttle.
- /benchmarks data availability confirmed: /v1/models catalog (live, 11 models) + /v1/benchmarks/leaderboard (live) sufficient for Cost-of-Inference column in Days 11-15. Per-model time-series missing but optional (catalog snapshots + leaderboard movements are enough for first articles).
- Days 16-25 multi-source ingest (lab blogs first) deferred per council synthesis: avoids reinforcing "AI demo trash" perception by piling on aggregation volume before the differentiated moat column lands.
- 47/47 news_drafter tests green. black + ruff full-tree clean.
FeatureInformationalAPIWebAction required: No
News pipeline strategy ship — containment + cost throttle (brutal audit response)
CEO ran a brutal enterprise audit of /news + flagged OpenRouter spend at $5.03/day actual = $151/mo trajectory (at cap). Four-seat thinktank council dispatched (codex returned reasoned verdict; gemini + claude-headless + droid silent — same harness fragility). Verdict: Option A + D containment — remove /news from top nav as immediate containment, then relaunch as analyst surface focused on LLM unit economics + benchmark-backed analysis. Full plan at docs/strategy/2026-05-21-news-pipeline-strategy.md.
- Fixed in:
- API v1.34.3 + Web v1.27.6
- Components:
- apps/web/src/components/landing/LandingPage.tsx — removed /news from navLinks · apps/web/src/app/[locale]/(unauth)/news/NewsPageClient.tsx — H1, subhead, title link, de-dupe attribution · api/app/services/news_drafter.py — banned clickbait headline templates · api/app/services/news_thumbnail_library.py — throttled topup cron (3 → 1 per cat per run) · docs/strategy/2026-05-21-news-pipeline-strategy.md (new)
Show details ▾Hide details ▴
- Cost throttle: NEWS_THUMBNAIL_TOPUP_MAX_PER_CATEGORY_PER_RUN default 3 → 1. Worst-case daily Nano Banana spend $11.52 → $3.84 during initial fill; ~$0.50/day at steady state.
- Env-tunable: NEWS_THUMBNAIL_TOPUP_MAX_PER_CATEGORY_PER_RUN + NEWS_THUMBNAIL_LIBRARY_TARGET_MIN. CEO can raise back when budget allows. Read each cron tick (no redeploy).
- Removed /news from top nav (route stays for sitemap + JSON-LD + AEO discovery). Re-add when content pivots to original analysis.
- Renamed H1 "AI News" → "Intelligence" — competitor brand collision (artificialintelligence-news.com = AINEWS).
- Dropped "hand-curated" lie from subhead. Replaced with truthful copy: "Daily signal on AI model releases, inference economics, agent tooling, and governance — surfaced from the developer-engineering community with our analysis."
- Listing card title now links to on-domain /news/[slug] instead of external Reddit URL. Stops handing SEO + attention to Reddit.
- De-duped source attribution: was 3× per card (top pill + title-as-link + footer "Originally from"). Reduced to 1× (top pill).
- Drafter prompt — banned clickbait headline templates ("Here's Why It Matters", "What X Should Know", "X Just Got Y", trailing rhetorical questions, numbered listicles, "X Is the New Y" clichés).
- tsc + black + ruff all clean. Tests still green (78 in news_drafter + thumbnail_library + ingest sweep).
Bug fixInformationalAPIAction required: No
Drafter re-applies title-blocklist at claim time — cleans legacy queue of pre-v1.34.0 items
v1.34.0 gated NEW ingest items but the queue already had hundreds of pre-v1.34.0 items with bad titles (help requests, weekly threads, general questions) that the drafter would still happily pick + publish. v1.34.1 re-applies the title-blocklist gate at drafter claim time so legacy queue items get rejected with rejection_reason="quality_gate: title_blocklist" and the drafter advances to the next candidate. ups/num_comments aren't stored on news_items so only title is re-checked here; ingest-side gate covers the other two layers for new items.
- Fixed in:
- API v1.34.1
- Components:
- api/app/services/news_drafter.py — _passes_quality_gate re-check after _claim_one_undrafted
Show details ▾Hide details ▴
- Within ~30 drafter ticks (one per hour) the legacy queue is fully cleansed of blocklisted titles.
- 78 news_drafter+ingest tests still green. No new tests needed — the gate function itself is exhaustively covered in v1.34.0.
FeatureInformationalAPIAction required: No
News drafter now filters for important news — 3-layer quality gate at ingest
Pre-v1.34.0 the drafter saw EVERY post that bubbled to the top of each subreddit RSS — including help requests ("anyone using X?"), weekly meta threads, general questions. Drafter picked the newest and published it indistinguishably from a real announcement. CEO directive 2026-05-21: "I don't want to post just about anything. It should be important news." The fix: 3-layer quality gate at ingest time — title regex blocklist + min_ups threshold (default 50) + min_comments threshold (default 10). Both ups AND comments must clear (AND, not OR). Data source upgraded from RSS to Reddit JSON API since RSS doesn't expose ups/comments. RSS remains the fallback when JSON fails.
- Fixed in:
- API v1.34.0
- Components:
- api/app/routers/v1/cron.py — _fetch_reddit_json + _passes_quality_gate helpers · api/tests/test_news_ingest_quality_gate.py — 23 new tests · api/tests/test_cron_news_ingest_reddit.py — autouse fixture for legacy RSS-path tests
Show details ▾Hide details ▴
- Title regex blocklist rejects ^(anyone|need help|how do i|how to|can someone|question|weekly|monthly|mod post|simple questions thread|d:?\s|\[d\]) — case insensitive.
- min_ups default 50, min_comments default 10 — env-tunable via NEWS_INGEST_MIN_UPS / NEWS_INGEST_MIN_COMMENTS / NEWS_INGEST_TITLE_BLOCKLIST.
- Both thresholds must clear simultaneously — high-ups-zero-comments = cross-posted meme, high-comments-low-ups = controversial flame thread; neither is news.
- Reddit JSON API replaces RSS for the primary fetch path (RSS doesn't expose ups/comments). Same per-subreddit rate budget. RSS is the resilience fallback.
- per_source response now includes passed_quality_gate count + rejected_by_reason dict — lets ops tune thresholds based on rejection mix.
- Env vars read on each call (not module load) — flyctl secrets set takes effect on next cron tick without redeploy.
- Reversible in one env change: NEWS_INGEST_MIN_UPS=0 NEWS_INGEST_MIN_COMMENTS=0 NEWS_INGEST_TITLE_BLOCKLIST="" returns to pre-v1.34.0 behavior.
- 31 tests green (23 new gate-specific + 8 updated RSS-path with autouse fixture). black + ruff clean.
Bug fixInformationalWebAction required: No
/news category filter — was showing 0 articles for every category (3-way schema drift fix)
CEO-caught 2026-05-21: filter showed "All (30)" but every category chip showed 0. Root cause: 3-way schema drift on the category enum. DB column is VARCHAR(64) with no CHECK constraint. Drafter prompt emits "Industry News | Tooling | Research | Models" (4 categories). Frontend hardcoded a different 7-item list ("Models | Funding | Tools | Papers | Infrastructure | Agents | Policy") that had only ONE overlap with what the drafter actually writes. So filtering by anything other than "Models" returned zero matches. Fixed by deriving the chip list from the actual articles loaded — adding a new drafter category never breaks the page again.
- Fixed in:
- Web v1.27.5
- Components:
- apps/web/src/app/[locale]/(unauth)/news/NewsPageClient.tsx (buildAllTabs + getCategoryDot with fallback) · apps/web/src/libs/gotcontext-api.ts (NewsCategory widened from 7-item union to string)
Show details ▾Hide details ▴
- Chip list now derived from data: `buildAllTabs(articles)` returns "All" + every distinct category present in the articles prop, sorted alphabetically.
- CategoryPill uses `getCategoryDot(category)` with a fallback to `bg-gcMuted` so unknown categories still render with a neutral dot instead of an undefined-class.
- CATEGORY_DOT map kept both v1.33.0 categories (Industry News, Tooling, Research, Models) AND the legacy 7 (Models, Funding, Tools, Papers, Infrastructure, Agents, Policy) so any historical articles still get their canonical color.
- NewsCategory TS type widened from a 7-item union to plain `string` — matches the DB schema (no CHECK constraint).
- No backend change. Counts now show real values: Industry News (24), Tooling (22), Research (9) as of fix time.
- tsc --noEmit clean.
FeatureInformationalAPIAction required: No
News drafter — 6 GEO/AEO structural rules so articles are citation-eligible in ChatGPT / Perplexity / Claude / Gemini
Single-seat thinktank verdict (codex gpt-5.5) on the v1.34 strategic direction question: structural GEO opt is the highest-leverage next ship — cheap, fast, measurable, compounds across every future article. Six new rules added to DRAFTER_SYSTEM_PROMPT shape every drafted article toward AI Search citation eligibility. Strategy doc at docs/strategy/2026-05-21-v134-strategic-direction.md.
- Fixed in:
- API v1.33.0
- Components:
- api/app/services/news_drafter.py — DRAFTER_SYSTEM_PROMPT (new GEO/AEO STRUCTURE section) · docs/strategy/2026-05-21-v134-strategic-direction.md (new)
Show details ▾Hide details ▴
- LEDE-FIRST: first sentence answers who/what/when/why. Bans "Surprisingly," "Counterintuitively," "What if," "Imagine," "It turns out," "Here's the thing" openers — crawlers extract the first sentence as the citation snippet.
- STATISTICS-IN-PARAGRAPH-2: concrete data point (percent, count, $, benchmark) included verbatim. Numeric anchors rank higher in AI Overviews.
- NAMED-SOURCE-IN-BODY: lab / company / author named in the body, not only in source_attribution. Named entities are the canonical AI ranking signal.
- NO-COUNTERINTUITIVE-OPENING: counterintuitive sentence required by VOICE must appear in paragraph 2 or later — never first.
- CONCRETE-CLAIM-PARA-2: paragraph 2 must contain at least one verbatim claim from the source (quoted or paraphrased).
- EXCERPT-AS-MINI-LEDE: the excerpt feeds JSON-LD description + OG meta + listing card — must self-contained answer who/what.
- Measurement: news_article_viewed.referrer_category="ai_search" via PostHog v1.27.4 instrumentation; 7-day cohort comparison before/after 2026-05-22.
- 47/47 news_drafter tests green; no kill words leak into prompt; black + ruff clean.
FeatureInformationalWebAction required: No
/news metrics instrumentation — pageview + scroll-depth + source-click + AI Search referral attribution
CEO directive 2026-05-21: "we need to think about metrics, what articles get visits and clicks. we need to be smart about this and track what works and what does not work." Exa research (multiple 2026 publisher-analytics sources) converged on four metrics that matter for AI-news sites: pageview + scroll-depth quartiles + source-click conversion + AI Search referral attribution. Phase 1 wires all four via PostHog so we start collecting data immediately. Phase 2 will surface them as an admin dashboard widget once we have 24-48h of real signal. Phase 3 will add engagement time + recirculation + a ROSA-style classifier.
- Fixed in:
- Web v1.27.4
- Components:
- apps/web/src/libs/posthog-events.ts (4 new helpers + referrer categorizer) · apps/web/src/app/[locale]/(unauth)/news/[slug]/NewsArticleClient.tsx (mount + scroll handler + click handler) · apps/web/src/app/[locale]/(unauth)/news/NewsPageClient.tsx (listing mount)
Show details ▾Hide details ▴
- news_article_viewed — fires once per /news/[slug] mount. Captures slug + category + source_attribution + referrer_category.
- news_article_scroll_depth — fires at 25/50/75/100% milestones, throttled to once per milestone per page load.
- news_source_clicked — fires when the reader clicks through to the external source URL. Closest analog to a conversion event on the news surface.
- news_listing_viewed — fires on /news index mount with article_count + referrer_category.
- referrer categorization splits AI Search (ChatGPT, Perplexity, Claude, Gemini, Copilot, Kagi, You.com) from traditional Search (Google, Bing, DDG, Brave) from Social (Twitter/X, Reddit, HN, LinkedIn, Mastodon) from Internal (gotcontext.ai itself) from Direct (empty referrer) from Other. GA4 lumps AI Search under "Other"; we want it as a first-class channel.
- mountFiredRef + depthFiredRef guard against React 18 strict-mode double-fire.
- tsc --noEmit clean.
FeatureInformationalWebAction required: No
/news SEO upgrade — NewsArticle JSON-LD enhancements + per-article sitemap entries
Two changes that improve discoverability without touching article content. (1) NewsArticle JSON-LD now carries mainEntityOfPage (canonical entity URL), articleSection (category — used by AI Overview crawlers for topical clustering), inLanguage (geographic eligibility signal), isAccessibleForFree (Google News inclusion requirement), and image as ImageObject (instead of bare URL — eliminates Google structured-data warnings). (2) sitemap.xml now enumerates every published /news/{slug} URL with proper lastmod from updated_at — Google can crawl + index articles individually rather than only seeing the /news index page. Both reference: docs/audits/2026-05-21-news-seo-research.md. Author/publisher stay as Organization (no Person bio per anonymous-byline rule).
- Fixed in:
- Web v1.27.3
- Components:
- apps/web/src/app/[locale]/(unauth)/news/[slug]/NewsArticleClient.tsx (JSON-LD additions) · apps/web/src/app/sitemap.ts (article enumeration with best-effort fetch)
Show details ▾Hide details ▴
- JSON-LD mainEntityOfPage points at the article URL — disambiguates this URL as the canonical entity for AI search.
- JSON-LD articleSection mirrors the news category — feeds Google AI Overview / Perplexity topical-cluster ranking.
- JSON-LD inLanguage explicit "en" — improves eligibility on geo-fenced AI surfaces.
- JSON-LD isAccessibleForFree true — required for Google News indexing.
- JSON-LD image upgraded from bare string to ImageObject {url, width: 1200, height: 675} — silences Google structured-data tester warnings.
- Sitemap now includes up to 200 article URLs per locale, each with its own lastmod. Fallback: if API down at build time, static routes still ship (no build failure).
- tsc --noEmit clean.
Bug fixInformationalWebAction required: No
/news thumbnails sized like thumbnails — not stretched magazine heroes
CEO-caught 2026-05-21: hero images on `/news` listing rendered as full-width 160px-tall banners that stretched to ~1000px wide on desktop, and the article detail page rendered a full-width 16:9 hero. The admin draft queue showed the same images at 96px square and looked correct. Fix: listing now renders as a fixed 160x100 16:10 thumbnail beside the article content (3-column row: date | thumbnail | text). Detail page caps the inline thumbnail at 280px max-width, 16:10 aspect. The full image URL is still emitted in the OG meta tag and the NewsArticle JSON-LD schema for AI Overview crawlers + social-share previews — those consumers need the full-resolution image, the on-page render does not.
- Fixed in:
- Web v1.27.2
- Components:
- apps/web/src/app/[locale]/(unauth)/news/NewsPageClient.tsx (listing card thumbnail) · apps/web/src/app/[locale]/(unauth)/news/[slug]/NewsArticleClient.tsx (detail page thumbnail)
Show details ▾Hide details ▴
- Listing card: was h-[160px] w-full inside content column — now h-[100px] w-[160px] sm:flex-row column beside content text.
- Detail page: was mt-8 aspect-[16/9] w-full — now mt-6 aspect-[16/10] w-full max-w-[280px].
- No backend change. Same `hero_image_url` field; only the render dimensions changed.
- tsc --noEmit clean.
Bug fixInformationalAPIAction required: No
News thumbnails: 31% first-pass → expected ~80% via retry + hourly backfill
Production audit 2026-05-21 found only 12 of 38 articles (31%) had hero thumbnails. Sentry confirmed every OpenRouter call returned 200 OK with span duration 5.6-11.9s — not timeout, not 5xx. Diagnosis: Gemini's safety filter returns a text-only response with no `images` array on Reddit-sourced titles that mention jailbreaks, exploits, ban votes, or security issues. Three-layer fix: (1) retry once with a category-only fallback prompt that drops the (possibly flagged) title/excerpt; (2) parse-fail log now includes Gemini's refusal text preview so the next session has direct telemetry; (3) new hourly backfill cron at :30 sweeps `hero_image_url IS NULL` articles from the last 48 hours, capped at 10/run, each row committed independently.
- Fixed in:
- API v1.32.12
- Components:
- api/app/services/news_thumbnail.py (retry + safe fallback prompt + backfill_missing_thumbnails) · api/app/routers/v1/cron.py (POST /v1/cron/news-thumbnail-backfill) · api/app/middleware/auth.py (_AUTH_BYPASS_PATHS entry) · .github/workflows/cron.yml (30 * * * * schedule + workflow_dispatch choice + case map)
Show details ▾Hide details ▴
- Cost ceiling stays $150/mo cap; worst case ~$0.0774/article (both attempts billed by OpenRouter when both produce no image).
- Backfill window is 48 hours so AI Overviews + social-share crawlers still benefit on first index. Older articles skipped to keep budget bounded.
- W14 cascade lock honored: handler + auth-bypass entry + workflow case map in the same commit. `test_cron_auth_bypass_parity.py` re-verified.
- Regression locked by 8 tests in test_news_thumbnail_v1_32_12.py covering fallback prompt content, retry trigger, no-retry on success, double-fail return, parser canonical + text-only shapes, and backfill empty-result contract.
Bug fixCriticalAPIAction required: No
P0 — news ingest silently fell back to 1 source; durable 8-subreddit seed via Alembic 0053
v1.32.10 expanded news ingest from 1 source to 8, but the seed INSERTs were applied via `flyctl ssh` rather than as an Alembic migration. Fly machines are ephemeral — a recycle or fresh DB drops the seeds and the cron silently falls back to `_NEWS_INGEST_DEFAULT_SOURCES` (single source: r/MachineLearning). Production impact 2026-05-21: 38 articles published that day, ZERO from 6 of the 8 intended subreddits. Only r/MachineLearning (29) and r/OpenAI (9, on the not-yet-recycled machine) made it through. v1.32.11 ships Alembic migration 0053 with `INSERT ... ON CONFLICT (slug) DO NOTHING` so the seed survives every fresh deploy.
- Fixed in:
- API v1.32.11
- Components:
- api/alembic/versions/0053_seed_news_sources_eight_subreddits.py (new idempotent seed migration) · api/tests/test_migration_0053_news_sources_seed.py (5 lock tests)
Show details ▾Hide details ▴
- Seed list: r/MachineLearning, r/OpenAI, r/LocalLLaMA, r/LLMDevs, r/AI_Agents, r/ClaudeCode, r/ClaudeAI, r/GeminiAI.
- Verified live post-deploy: cron returned sources_attempted=8, all 8 slugs present in per_source breakdown, 13 new articles ingested + 187 dedup'd on the first manual trigger.
- Regression lock asserts exactly 8 distinct slugs, all Reddit RSS URLs, all `r/<Sub>` display names. Any future shrink/typo fails locally before push.
- Lesson encoded: feature data that needs to survive deploys MUST go through Alembic, never `flyctl ssh`. Manual SQL is for one-shot ops, not for state the feature depends on.
FeatureInformationalAPIAction required: No
News ingest now pulls from 8 subreddits (engineering + Claude Code + official Claude / OpenAI / Gemini lab feeds)
The /v1/cron/news-ingest-reddit cron previously hardcoded a single URL. v1.32.10 generalized it to iterate over every active row in the news_sources table where kind=rss. CEO Reddit market analysis (2026-05-21) expanded the source set from r/MachineLearning alone to 8 subreddits spanning AI engineering practitioners (r/LocalLLaMA, r/LLMDevs, r/AI_Agents), Claude Code workflows (r/ClaudeCode, r/ClaudeAI), and official lab feeds (r/OpenAI, r/GeminiAI). Per-source error handling so one bad feed does not abort the run.
- Fixed in:
- API v1.32.10
- Components:
- api/app/routers/v1/cron.py (news_ingest_reddit handler refactored) · news_sources table — 7 new rows inserted via DB migration
Show details ▾Hide details ▴
- Source set: r/MachineLearning (research papers), r/LocalLLaMA (local LLM ops), r/LLMDevs (app-layer LLM engineering), r/AI_Agents (MCP + multi-agent), r/ClaudeCode (Claude Code workflows), r/ClaudeAI (broader Anthropic ecosystem), r/OpenAI (OpenAI lab feed), r/GeminiAI (Google Gemini feed).
- Cap is per-source: 50 entries/source/cycle × 8 sources = 400 max items/cycle worst case.
- Response shape adds sources_attempted (count) and per_source (per-slug feed_entries + optional error).
- Drafter cron at :25 picks 5 oldest undrafted per cycle — diverse source mix builds the queue, drafter samples from it.
Bug fixInformationalAPIAction required: No
Hotfix — v1.32.8 thumbnails uploaded to wrong Supabase bucket; public URLs 404'd
v1.32.8 shipped Nano Banana thumbnail generation, but the bucket selection logic read SUPABASE_STORAGE_BUCKET env var which is set to kb-uploads-dev on Fly (KB Phase 1's bucket). Uploads succeeded via service-role auth (bypassing RLS) but public reads returned 404 "Bucket not found" because kb-uploads-dev is configured private. The dedicated news-thumbnails bucket (provisioned during v1.32.8 orchestration) sat empty because nothing wrote to it. Caught immediately via post-merge smoke fire on the first :25 drafter cron after v1.32.8 deploy.
- Fixed in:
- API v1.32.9
- Components:
- api/app/services/news_thumbnail.py (env var name change)
Show details ▾Hide details ▴
- Changed env var read from SUPABASE_STORAGE_BUCKET to NEWS_THUMBNAIL_BUCKET (dedicated) with the same default of "news-thumbnails".
- Future thumbnails will hit the right bucket; 5 orphaned URLs already in the DB were UPDATE ... SET draft_thumbnail_url=NULL (those drafts are still approve-able, just without image previews).
- Regression locked by TestNewsThumbnailBucketEnvVar in test_news_thumbnail.py.
- Lesson: when adding a new feature that needs its own infrastructure (bucket / Redis prefix / etc.), use a DEDICATED env var name. Don't reuse an existing one — the existing one is set for a different purpose and the override silently breaks the new feature.
FeatureInformationalAPIAction required: No
News drafter now generates a hero thumbnail per draft via Gemini 2.5 Flash Image (Nano Banana)
Every news draft now gets an original AI-generated 16:9 editorial thumbnail uploaded to Supabase Storage at draft time. Image generation runs concurrently with the text drafter via asyncio.gather so per-draft wall time stays at ~8s instead of doubling. License-clean (Google Gemini terms allow commercial use). Cost ~$0.04 per draft × 5/hr × 24h ≈ $144/mo at default cadence — CEO-approved with hard monthly cap NEWS_THUMBNAIL_MONTHLY_BUDGET_USD default 150 and skip-on-exceed semantics.
- Fixed in:
- API v1.32.8
- Components:
- api/app/services/news_thumbnail.py (new service module) · api/app/services/news_drafter.py (parallel text + image generation) · api/alembic/versions/0052_news_items_draft_thumbnail_url.py (new column) · api/app/routers/v1/news_drafts.py (response model) · apps/web/src/libs/gotcontext-api.ts (typed seam) · ~/.claude/skills/use-nano-banana/SKILL.md (new workstation-wide skill) · ~/.claude/skills/find-best-thumbnail-via-exa/SKILL.md (new workstation-wide skill)
Show details ▾Hide details ▴
- Model: google/gemini-2.5-flash-image (NOT *-preview which 404s on OpenRouter).
- Wire shape: choices[0].message.images[0].image_url.url contains data:image/png;base64,... payload (NOT message.content which is a text description).
- Atomic best-effort: image failure does not block text drafting; either path can fail and the draft still completes.
- 17 new TDD tests in test_news_thumbnail.py; W14 full sweep 2118 pass / 0 fail.
- DraftCard frontend img preview in admin queue is deferred — thumbnail URLs are already on the API response shape, only the rendered preview is missing.
Bug fixInformationalAPIAction required: No
Hotfix — admin "Approve" 500'd because news_service.py still bound isoformat strings to asyncpg timestamptz
Every admin click on Approve draft returned HTTP 500. Root cause via Sentry GOTCONTEXT-API-C: news_service.py had three now_iso = now.isoformat() sites (publish_article, edit_article, withdraw_article) binding strings to published_at/created_at/updated_at timestamptz columns. asyncpg requires datetime objects. v1.32.2 fixed the same pattern in news_drafter.py at 4 sites but missed the 3 sites in news_service.py that the approve flow hits. Caught the moment the CEO clicked Approve on the first real draft.
- Fixed in:
- API v1.32.7
- Components:
- api/app/services/news_service.py (3 sites: publish_article, edit_article, withdraw_article)
Show details ▾Hide details ▴
- Assigned now_iso = now (datetime) at all 3 sites; kept the variable name to minimize diff.
- Regression locked by TestNewsServiceNoIsoformatOnDatetimeBinds — grep test that asserts no now_iso = now.isoformat() pattern exists in news_service.py.
- Same regression CLASS as v1.30.2 (news_service.publish_article first time) and v1.32.2 (drafter file). The pattern keeps re-appearing because timestamptz binding via raw SQL text() is split across multiple service files.
FeatureInformationalAPIAction required: No
News drafts auto-publish 1 hour after entering the queue (CEO directive)
Drafts that go through human review still publish immediately on click — no behavior change there. Drafts that sit untouched for 60 minutes now get auto-promoted to news_articles by a new /v1/cron/news-auto-publish endpoint running every 30 min at :15 and :45 via GitHub Actions (20 min buffer after the :25 hourly drafter). The audit trail records actor_clerk_id="system:auto-publish" so admin queries can distinguish auto-published articles from human-approved ones. This unblocks low-touch content velocity: the editor reviews when they want, and stale drafts don't pile up indefinitely.
- Fixed in:
- API v1.32.6
- Components:
- api/app/services/news_drafter.py (auto_publish_overdue_drafts service) · api/app/routers/v1/cron.py (POST /v1/cron/news-auto-publish) · api/app/middleware/auth.py (bypass entry for the new cron path) · .github/workflows/cron.yml (15,45 * * * * schedule + workflow_dispatch option)
Show details ▾Hide details ▴
- Atomic claim pattern with FOR UPDATE SKIP LOCKED on the inner SELECT — concurrent cron runs never double-publish.
- On per-item failure: row is rolled back to drafted state so the next cron retries instead of leaving it stuck in publishing.
- Default threshold is 60 minutes per CEO directive; max_age_minutes parameter on the service helper allows per-source overrides in future work.
- Reuses approve_draft helper for DRY publishing logic — same code path as the human-click /v1/news/admin/drafts/{id}/approve endpoint.
- Sentinel actor "system:auto-publish" written to news_items.reviewed_by_user_id distinguishes auto vs human in the audit trail.
- Regression locked by 4 tests in TestAutoPublishCronAuthBypass + TestAutoPublishOverdueDrafts.
Bug fixInformationalAPIAction required: No
Hotfix — admin drafts queue showed empty bodies because the LIST endpoint dropped draft_body_md from the response
Once the admin drafts page rendered cleanly (post-v1.32.4 web hotfix), expanding any draft for edit/preview showed an empty body. The drafter was generating real 3-4KB markdown bodies and writing them to news_items.draft_body_md correctly, but the GET /v1/news/admin/drafts LIST response was silently dropping the column. Subagent oversight from the v1.32.0 ship — DraftItemResponse listed every other draft column but missed the one consumers actually need most.
- Fixed in:
- API v1.32.5
- Components:
- api/app/routers/v1/news_drafts.py (DraftItemResponse + _draft_to_response)
Show details ▾Hide details ▴
- Added draft_body_md: Optional[str] to the DraftItemResponse Pydantic model.
- Added draft_body_md=item.draft_body_md to the _draft_to_response helper.
- Regression locked by TestDraftListResponseIncludesBody in api/tests/test_news_drafter.py.
Bug fixInformationalWebAction required: No
Hotfix — admin pages crashed once auth worked because the listNewsDrafts type didn't match the backend response shape
After v1.32.3 fixed the AuthMiddleware bypass shadow and admin auth started working, /dashboard/admin and /dashboard/admin/news/drafts both immediately rendered the dashboard error boundary with "Cannot read properties of undefined (reading 'length')". Root cause via Sentry source-mapping (GOTCONTEXT-WEB-B): the listNewsDrafts helper in apps/web/src/libs/gotcontext-api.ts claimed the backend returned a {items, total} wrapper, but the route is response_model=list[DraftItemResponse] which serializes as a bare JSON array. Pre-v1.32.3 the helper always threw on the 403, hiding the type mismatch. Once auth worked, the helper returned the bare array, drafts.items was undefined, and drafts.items.length crashed. F11 backend-frontend seam mismatch class — same regression class encoded in feedback_backend_frontend_seam_audit.md.
- Fixed in:
- Web v1.32.4
- Components:
- apps/web/src/libs/gotcontext-api.ts (listNewsDrafts return type) · apps/web/src/app/[locale]/(auth)/dashboard/admin/page.tsx (KPI fetch) · apps/web/src/app/[locale]/(auth)/dashboard/admin/news/drafts/page.tsx (queue fetch)
Show details ▾Hide details ▴
- Changed return type from Promise<NewsDraftListResponse> to Promise<NewsDraftItem[]>.
- Both consumers now read the bare array via Array.isArray() guard for defensive depth.
- Lesson encoded: F11 audits MUST cross-check backend response_model declarations against the matching frontend TypeScript return-type before merge. tsc only catches "field exists" not "wire shape matches" — the backend Pydantic model declares what FastAPI actually emits.
Bug fixInformationalAPIAction required: No
Hotfix — admin drafts queue 403'd every click because the public-news GET bypass shadowed the admin sub-prefix
CEO clicked Review news drafts from the admin landing page and the destination page surfaced "Admin access required". JWT inspection via claude-in-chrome MCP confirmed the Clerk sub claim matched _ADMIN_USER_IDS exactly + the email row in users was correct + the resolver path returned the right email and plan when run directly on the live machine. Root cause was that v1.29.1's public-news GET bypass at "/v1/news/" silently matched /v1/news/admin/drafts (which starts with /v1/news/), so AuthMiddleware returned call_next without populating request.state.user_id or request.state.email. The downstream _require_admin then read empty state and 403'd every admin click. The Clerk JWT was being silently ignored.
- Fixed in:
- API v1.32.3
- Components:
- api/app/middleware/auth.py (added _AUTH_BYPASS_GET_PREFIX_EXCLUDES + gate check)
Show details ▾Hide details ▴
- New _AUTH_BYPASS_GET_PREFIX_EXCLUDES tuple lists sub-prefixes that must NOT be bypassed even when the parent prefix is in _AUTH_BYPASS_GET_PREFIXES.
- Initial exclude: "/v1/news/admin/" — keeps /v1/news/{slug} public reads bypassing AuthMiddleware while /v1/news/admin/drafts (admin-gated) goes through the full auth flow.
- Regression locked by TestNewsAdminDraftsAuthMiddlewarePassthrough in api/tests/test_news_drafter.py.
- Same class as v1.32.1 (missing AuthMiddleware bypass for news-draft cron) — auth-middleware path-matching bugs are easy to introduce when adding new variable-path public routes.
Bug fixInformationalAPIAction required: No
Hotfix — news-draft cron 500'd because asyncpg requires datetime objects, not isoformat strings
Second smoke fire on /v1/cron/news-draft (after v1.32.1's AuthMiddleware bypass fix) returned HTTP 500. Sentry surfaced the actual error: DBAPIError invalid input for query argument $9: '2026-05-21T00:42:31...+00:00' (expected datetime, got str). Root cause: news_drafter.py at 4 sites computed now = datetime.now(tz=timezone.utc).isoformat() then bound that string to drafted_at / reviewed_at SQL columns. asyncpg requires the bare datetime object — it does its own serialization. Same class as the v1.30.2 hotfix; the v1.32.0 subagent re-introduced the pattern. Fixed by dropping the .isoformat() at all 4 call sites. The public .isoformat() on the response payload at line 1047 stayed (correct usage there: serializing back to JSON).
- Fixed in:
- API v1.32.2
- Components:
- api/app/services/news_drafter.py (4 line fix)
Show details ▾Hide details ▴
- Caught via Sentry MCP search after flyctl log tail cap (100 lines) was too narrow to surface the bottom of the Starlette middleware-chain traceback.
- No production cron fires were lost — hotfix shipped before the next hourly :25 trigger.
- Customer-perception window of broken state: ~25 minutes between v1.32.1 promote-to-prod and v1.32.2 fix landing.
Bug fixInformationalAPIAction required: No
Hotfix — /v1/cron/news-draft was 401'ing every cron fire because the AuthMiddleware bypass was missed
v1.32.0 shipped the news drafter cron endpoint at /v1/cron/news-draft but missed adding it to AuthMiddleware's bypass list. Every hourly cron fire (scheduled at :25 via GitHub Actions) would have been blocked with a 401 "Missing or malformed Authorization header" before _require_cron_secret() in the route handler ever ran. Caught immediately by the post-merge smoke fire — no production cron fires were lost. Same class of bug as the v1.29.1 hotfix for /v1/cron/news-ingest-reddit. The pattern is now codified in CLAUDE.md so future cron endpoints don't miss the same step.
- Fixed in:
- API v1.32.1
- Components:
- api/app/middleware/auth.py (added /v1/cron/news-draft to _BYPASS_PATHS)
Show details ▾Hide details ▴
- 1-line fix: added "/v1/cron/news-draft" to the _BYPASS_PATHS list alongside the other 14 cron endpoints already bypassing AuthMiddleware.
- Customer-perception window of broken state: 17 minutes (between v1.32.0 promote-to-prod and the orchestrator smoke fire that caught the 401).
FeatureInformationalAPIWebAction required: No
In-tree AI News drafter pipeline + admin approval queue — replaces the never-built n8n drafter
The /news section had 26 ingested Reddit items rotting since v1.30.0 because no drafter ever existed end-to-end (n8n was the intended owner per the v1.29 architecture; audit on 2026-05-20 found the n8n instance had only the ingest workflow we already pivoted in-tree, zero drafting workflow). v1.32.0 ships the missing piece in-tree: a hourly cron at :25 claims undrafted news_items via atomic UPDATE...RETURNING + FOR UPDATE SKIP LOCKED, calls OpenRouter to generate a publication-ready draft, runs Tier-1 + Tier-2 kill-word validation and anonymous-ownership scrubbing, and queues drafts for editor approval in a new admin dashboard. Default model claude-haiku-4-5 at ~$0.005 per draft; monthly budget cap default $25 with skip-on-exceed.
Show details ▾Hide details ▴
- Atomic single-row claim pattern UPDATE ... WHERE draft_status='undrafted' RETURNING with FOR UPDATE SKIP LOCKED on the inner SELECT — concurrent cron runs cannot double-draft the same item.
- Drafter validation: every LLM output sweeps the Tier-1 kill-word list (delve, utilize, leverage, etc.) + structural patterns (it's not just X but Y) + anonymous-ownership regex (rejects Founder/CEO/Owner/Creator/Built by paired with personal names). Two failures auto-reject.
- Admin queue at /dashboard/admin/news/drafts shows pending drafts with original source URL + edit-in-place (title, excerpt, category, body markdown) + approve-and-publish + reject-with-reason.
- Resend email digest sent at end of each cron run if drafted_count > 0.
- 36 pytest tests + 6 Vitest tests for the admin UI. n8n retirement deferred to a separate commit AFTER first production draft is published end-to-end.
Bug fixInformationalAPIAction required: No
Hygiene — tensor-grep pin floor bumped 1.12.28 → 1.12.40 + audit of 6 subcommand signatures
CEO surfaced "tensor-grep is multiple versions behind" during priority reshuffle. Audit found the pin floor at >=1.12.28 (12 patches behind live PyPI latest 1.12.40), but production Fly machines were almost certainly already running the latest matching version since pip resolves >=1.12.28 to whatever's latest at install time. This bump documents reality and tightens what self-hosted Docker image operators install. No customer-visible behavior change.
Show details ▾Hide details ▴
- Per the CLAUDE.md rule "every tensor-grep>= pin bump must re-run tg <subcmd> --help for every subcommand we invoke," all 6 subcommands verified against tg 1.12.40 output: agent, blast-radius-render, callers, context-render, edit-plan, search. Zero flag drift, zero signature changes.
- Changelog 1.12.28 → 1.12.40 delta is bug-fix-only: structured JSON error envelopes on tg search invalid-input paths (1.12.28), rg config-override flag forwarding (1.12.31), ripgrep-binary-resolution agent hardening (1.12.34, 1.12.40), GPU promotion evidence gates (1.12.36), ast-grep semantic flag routing through Python sidecar (1.12.38).
- Regression locks api/tests/test_tensor_grep_smoke.py + api/tests/test_blast_radius_service.py — 40/40 passing on tensor-grep 1.12.40.
Bug fixInformationalAPIWebAction required: No
Hotfix — Vercel prod deploys had been failing since 2026-05-19; reverted Clerk-state-aware marketing nav
Every Vercel production deploy since c16d048 (2026-05-19) failed at the prerender step with 'useAuth can only be used within the <ClerkProvider /> component'. Root cause: LandingPage::Navigation was made Clerk-state-aware via useAuth() to swap CTAs between signed-in and signed-out users; the 'use client' directive isn't enough — Next.js 16 prerenders all (unauth) routes at SSG time (including /benchmarks/submit and /authors/[slug]), importing Navigation from the layout, where ClerkProvider isn't yet in render scope. v1.31.0's News Center polish was therefore stuck on Vercel even though API + tests were green. Reverted the auth-aware swap; marketing nav always renders Sign In + Get free API key CTAs.
- Fixed in:
- API v1.31.1 / Web v1.27.1
- Components:
- apps/web/src/components/landing/LandingPage.tsx
Show details ▾Hide details ▴
- Removed useAuth() from Navigation. Removed UserButton import (no longer used).
- Marketing nav now always renders Sign In + Get free API key CTAs. Signed-in users still navigate fine via Dashboard link in the nav + footer + Cmd+K CommandPalette + /dashboard direct URL.
- Clerk v7 removed <SignedIn>/<SignedOut> in favor of <Show>; <Show> is async Server Component (incompatible with client-component Navigation). Re-introducing the auth-aware swap should go through next/dynamic client-only sub-component with ssr: false.
FeatureInformationalAPIWebAction required: No
News Center polish — discoverability, RSS, newsletter capture, markdown rendering, hero cards
The /news section was shipped in v1.29.0 but invisible: no nav entry, no footer, no sitemap, no CommandPalette, dead-end empty state. This release fixes all five discoverability locations, replaces the dead-end empty state with a newsletter capture form, ships an RSS feed at /news/feed.xml, swaps the raw-string MarkdownBody for react-markdown, and adds reading-time + source-attribution + hero-image cards. Plan validated by an 8-seat thinktank council (7-of-8 valid seats); critical settlement was preserving the existing canonical=source_url ?? selfUrl pattern per architecture doc §11.2 (against the audit B-12 finding that would have flipped to self-canonical at 0 DA, triggering Google site-reputation-abuse penalties).
- Fixed in:
- API v1.31.0 / Web v1.27.0
- Components:
- apps/web/src/components/landing/LandingPage.tsx (navLinks) · apps/web/src/templates/Footer.tsx · apps/web/src/app/sitemap.ts · apps/web/src/components/CommandPalette.tsx · apps/web/src/app/[locale]/(auth)/dashboard/layout.tsx · apps/web/src/app/[locale]/(unauth)/news/NewsPageClient.tsx · apps/web/src/app/[locale]/(unauth)/news/[slug]/NewsArticleClient.tsx · apps/web/src/app/news/feed.xml/route.ts (new) · apps/web/src/app/robots.ts · apps/web/src/libs/gotcontext-api.ts (submitNewsletterLead) · apps/web/src/locales/en.json + fr.json (nav_news)
Show details ▾Hide details ▴
- Discoverability: /news now in LandingPage navLinks, Footer, sitemap.ts, CommandPalette (shortcut G N), and dashboard primaryMenu.
- Empty state replaced with a newsletter capture form posting to /v1/leads (existing endpoint); search + category tabs hidden on zero articles.
- RSS feed at /news/feed.xml (RSS 2.0, 50 most recent articles, 10-min Cache-Control). robots.ts references both sitemap.xml and the new feed.
- MarkdownBody replaced with react-markdown ^9.1.0 — full markdown semantics (lists, blockquotes, headings, bold, fenced code, external links).
- New ReadingTime component (~250wpm) rendered next to date in article header + cards.
- Source attribution panel made prominent with external-link icon on article detail.
- Hero-image cards via next/image with lazy-load; gradient fallback when no hero_image_url.
- Canonical URL preserved at source_url ?? selfUrl per architecture doc §11.2 (council overrode audit B-12).
- Invalid -tracking-wide Tailwind class removed; uppercase tracking-wider eyebrow swapped for font-mono (anti-AI-slop per gc-brand-voice).
- 28/28 news tests pass; check-types + lint clean.
Bug fixInformationalAPIAction required: No
Hotfix — news_service published_at bind: pass datetime, not isoformat string
news_service.ingest_news_item was passing published_at.isoformat() (a string) to the INSERT statement. SQLite (TEXT column) silently accepted this — the existing v1.29.0 dedup test stayed green — but Postgres + asyncpg rejects strings on TIMESTAMP columns. Latent bug surfaced when v1.30.0 news-ingest-reddit cron started feeding real Reddit RSS items: all 25 entries landed in the errors bucket with "expected a datetime.date or datetime.datetime instance, got str".
- Fixed in:
- API v1.30.2
- Components:
- api/app/services/news_service.py · POST /v1/cron/news-ingest-reddit
Show details ▾Hide details ▴
- Pass the datetime instance directly instead of .isoformat() string at news_service.py:293.
- New regression test test_ingest_news_item_passes_datetime_not_string captures the SQL bind parameters and asserts isinstance(published_at, datetime).
Bug fixInformationalAPIAction required: No
Hotfix — /v1/cron/news-ingest-reddit bypasses AuthMiddleware
v1.30.0 missed adding the new cron endpoint to _AUTH_BYPASS_PATHS in api/app/middleware/auth.py, so the global AuthMiddleware intercepted the X-Cron-Secret request and returned 401 before _require_cron_secret in cron.py could run. Same regression class as v1.29.1 (news GET endpoints). All 14 prior cron endpoints carry the bypass explicitly; this hotfix brings the new endpoint in line.
- Fixed in:
- API v1.30.1
- Components:
- POST /v1/cron/news-ingest-reddit · api/app/middleware/auth.py
Show details ▾Hide details ▴
- Added /v1/cron/news-ingest-reddit to _AUTH_BYPASS_PATHS exact-match set.
- New TestNewsIngestAuthMiddlewareBypass class in api/tests/test_cron_news_ingest_reddit.py locks the bypass invariant for the future.
FeatureInformationalAPIAction required: No
In-tree news ingest cron — replaces broken n8n Schedule Trigger
The v1.29.0 architecture chose n8n on Fly.io for AI News Center ingestion, but n8n 2.21.4 silently fails to register Schedule Trigger cron jobs (GitHub Issues #23943, #21922, #27103) — empirically verified on 2026-05-19: 4-minute wait on a `* * * * *` schedule produced 5x "Deregistered all crons" log lines with zero corresponding "Registered cron" entries, and zero rows in news_items. After an 8-seat thinktank council (7-of-7 valid seats endorsed the in-tree pivot), the ingest path moves into the FastAPI cron router while n8n stays only for the editorial drafting workflow (Telegram approve-gate is HTTP-triggered, unaffected by the bug).
- Fixed in:
- API v1.30.0
- Components:
- POST /v1/cron/news-ingest-reddit · api/requirements.txt (feedparser>=6.0.10) · .github/workflows/cron.yml
Show details ▾Hide details ▴
- New POST /v1/cron/news-ingest-reddit pulls r/MachineLearning RSS, normalizes via news_service.ingest_news_items_batch, dedups via UNIQUE(source_slug, external_id).
- Caps at 50 entries per cycle to bound burst on RSS spikes.
- Wired into .github/workflows/cron.yml at "7 * * * *" (hourly at :07).
- Added feedparser>=6.0.10 to api/requirements.txt — pure-Python, BSD-2-Clause, single-file dep.
- n8n stays for editorial drafting only (LLM-draft via OpenRouter -> Telegram approve-gate -> POST /v1/news/articles); the buggy n8n workflow was deactivated to stop the futile cron-deregistration loop.
- 7 new TDD tests in api/tests/test_cron_news_ingest_reddit.py covering auth gate, feed URL, empty/normalized batching, response shape, and the 50-entry cap.
Bug fixInformationalAPIAction required: No
Hotfix — public /v1/news GET endpoints bypass AuthMiddleware (Variant B SEO posture)
v1.29.0 shipped with GET /v1/news, /v1/news/categories, and /v1/news/{slug} requiring authentication, which contradicted the documented Variant B posture (reads must be public for SEO + content-marketing flywheel). This hotfix adds the read endpoints to the exact-path and GET-only prefix bypass sets. POST /v1/news/items + POST /v1/news/articles + PATCH /v1/news/articles/{id} stay authenticated.
- Fixed in:
- API v1.29.1
- Components:
- GET /v1/news · GET /v1/news/categories · GET /v1/news/{slug} · api/app/middleware/auth.py
Show details ▾Hide details ▴
- Added /v1/news + /v1/news/categories to _AUTH_BYPASS_PATHS (exact-match).
- Added /v1/news/ to _AUTH_BYPASS_GET_PREFIXES (GET-only — POST stays gated).
- New regression test api/tests/test_news_auth_bypass_regression.py (5 cases) locks the invariants + includes a defence-in-depth check that admin POST endpoints stay OUT of the exact-path bypass set.
FeatureInformationalAPIWebAction required: No
AI News Center — thin in-tree layer for n8n-driven ingestion + Telegram approve-gate
New /news routes (public read) + admin REST endpoints (n8n posts into) ship the storage and read surface for an AI news feed. The ingestion + drafting + human review pipeline runs externally in n8n with Telegram approve-gate, validated via Ref + Exa cross-research as the lowest-effort path that preserves SEO and brand voice. Variant B (human-in-the-loop) chosen over Variant A (auto-publish) to avoid Google Helpful Content de-indexing risk on the main domain.
- Fixed in:
- API v1.29.0 / Web v1.26.0
- Components:
- GET /v1/news · GET /v1/news/{slug} · GET /v1/news/categories · POST /v1/news/items (admin) · POST /v1/news/articles (admin) · PATCH /v1/news/articles/{id} (admin) · POST /v1/news/articles/{id}/withdraw (admin) · /news (public reader) · /news/[slug] (per-article)
Show details ▾Hide details ▴
- New Alembic 0050 — `news_sources`, `news_items`, `news_articles`, `news_article_revisions` (BEFORE-trigger immutability on revisions per KB Phase 1 pattern).
- Admin write endpoints gated on caller`s gc_ key resolving to a user whose clerk_id is in ADMIN_USER_IDS. Public reads have no auth (Variant B SEO posture — reads crawlable for content-marketing flywheel).
- DMCA-ready: `POST /v1/news/articles/{id}/withdraw` with `dmca: true` writes a `dmca_withdrawn` revision row; public route returns 410 Gone for withdrawn articles.
- Frontend: /news index rewired to server-fetch via `fetchNewsArticles()`; new /news/[slug] route with Article JSON-LD structured data + canonical link to original source.
- 22 backend TDD tests + 14 frontend TDD tests. Architecture doc at `docs/architecture/AI-NEWS-AGGREGATOR.md`, implementation plan at `docs/plans/2026-05-19-ai-news-aggregator-plan.md`.
- CEO post-deploy actions: mint admin gc_ key for n8n, deploy n8n on Fly (~$5/mo), fork the official "Summarize AI news from RSS, Reddit and HN with Claude" n8n template, add Telegram approve-gate.
FeatureInformationalAPIWebAction required: No
Architecture refresh — Resend bounce webhook, PostHog landing CTA wiring, billing-unification AST fence
Three independent tracks shipped from the 4-cycle architecture-doc convergence loop. Resend bounce webhook auto-suppresses email on hard bounces and complaints, protecting sender reputation. PostHog landing-page CTAs now emit click events (7 instrumented sites — hero, sign-up, pricing, calculator, docs). A new CI AST fence locks the v1.23.20 billing-unification so future dollar-printing functions must route through `model_pricing.estimate_cost`.
- Fixed in:
- API v1.28.0 / Web v1.25.0
- Components:
- POST /webhooks/resend · apps/web/src/components/landing/LandingPage.tsx · api/tests/test_billing_unification_fence.py · docs/architecture/SYSTEM-ARCHITECTURE.md
Show details ▾Hide details ▴
- Track A — `POST /webhooks/resend` Svix-verified endpoint. Hard bounces and complaints flip `users.email_opt_out=true` and write to `audit_logs` via `record_audit_event`. Soft bounce, delivered, opened, clicked → log-only no-op. Case-insensitive email lookup; idempotent on repeat events. Closes the long-standing missing-bounce-webhook product gap.
- Track B — PostHog landing-page CTA instrumentation. Three new helpers (`trackSignupCtaClicked`, `trackPricingCtaClicked`, `trackDocsCtaClicked`) plus existing `trackLandingHeroClicked` + `trackCalculatorCtaClicked` wired to 7 CTA sites. Inline-guard pattern silently no-ops when `NEXT_PUBLIC_POSTHOG_KEY` is unset. Closes the W1 conversion-funnel-read gap.
- Track C — `test_billing_unification_fence` AST scan in CI. New walker in `api/tests/test_billing_unification_fence.py` flags any function with `tokens * float_literal` or `tokens / 1000 * float_literal` that does not call `estimate_cost`. Meta-test with 7 crafted cases (4 positive, 3 negative) locks the walker itself. Current main is clean; synthetic-violation injection confirms the fence fails as expected.
- New architecture doc `docs/architecture/SYSTEM-ARCHITECTURE.md` (1453 lines, 22 sections + 6 appendices) — first-class system architecture writeup with multi-grader convergence log, KB v1-beta carry-forward patterns, worked `/v1/compress` REST code-trace, and the v1.28+ named tracks (Appendix F) all now shipped.
- `RESEND_WEBHOOK_SECRET` added to `_validate_webhook_secrets()` CRITICAL-log list on Fly production. Webhook returns 400 (no 5xx) when the secret is unset.
FeatureInformationalAPIWebAction required: No
AI Benchmark Repository — community-standard schema (multi-GPU, Apple Silicon, ITL, speculative decoding)
The benchmark schema now matches the fields used by NVIDIA NIM, vLLM, MLPerf, and the Apple Silicon community — making submitted results directly comparable to numbers published elsewhere. New hardware-class filter tabs (NVIDIA CUDA / AMD ROCm / Apple Silicon / Intel Arc) let you find relevant results without scrolling.
- Fixed in:
- API v1.27.0
- Components:
- /benchmarks leaderboard · /benchmarks/submit wizard · /benchmarks/runs/:slug result page · POST /v1/benchmarks/runs · GET /v1/benchmarks/leaderboard
Show details ▾Hide details ▴
- Multi-GPU support: submit results for 1×4090, 2×4090 NVLink, or 8×H100 configs — gpu_count is now a first-class field so outlier detection partitions correctly per configuration.
- Apple Silicon unified memory: a new memory_type field (discrete_vram / unified_memory / system_ram) gives MLX benchmarks a sensible slot; the M5 Max community benchmark vertical is now fully expressible.
- Inter-Token Latency (ITL): the canonical generation-phase metric used by NVIDIA NIM, vLLM, BentoML, and AWS Neuron is now capturable alongside tokens/sec, enabling apples-to-apples comparisons with production serving benchmarks.
- Speculative decoding and Flash Attention flags, perplexity dataset reference (WikiText-2 / PTB / C4 / ShareGPT), and typed runtime fields (cuda / rocm / metal / vulkan / openvino / cpu) added. 5 new diagnostic rules detect common performance pitfalls for ROCm, Apple Silicon, and EXL2 quants.
FeatureInformationalAPIWebMCP GatewayAction required: No
AI Benchmark Repository — submit wizard, leaderboard, permalinks, diagnostics, and MCP tool
The complete benchmark surface is now live: a 3-step submit wizard, sortable leaderboard with 4 ranking lenses (tokens/sec · $/Mtok · tokens/sec/watt · value composite), shareable per-run permalinks with automated diagnostics, and a `submit_benchmark` MCP tool so agents can log results programmatically.
- Fixed in:
- API v1.26.0
- Components:
- /benchmarks/submit · /benchmarks leaderboard · /benchmarks/:modelSlug per-model page · /benchmarks/runs/:slug permalink · MCP `submit_benchmark` tool · POST /v1/benchmarks/runs · GET /v1/benchmarks/leaderboard · GET /v1/benchmarks/runs/:id_or_slug
Show details ▾Hide details ▴
- Submit wizard: 3-step flow — model / quant / context / batch → hardware autocomplete (supports paste from nvidia-smi -q) → headline metrics + software stack + agent-submission toggle. Cloudflare Turnstile protects anonymous submissions.
- Leaderboard with 4 ranking lenses and verified badges: cyan = admin-verified, green = community-verified, grey = unverified, red = flagged. The 🤖 badge marks agent-submitted results.
- Per-run permalink pages include a comparison strip (median / this run / top-10% bar chart with percentile pill), 8 automated diagnostic rules that explain likely causes of underperformance, and a one-click copy of the reproducibility recipe as YAML or cURL.
- `submit_benchmark` MCP tool allows agents to log benchmark results directly from a CI pipeline or benchmark script. Returns the public permalink and outlier score immediately.
FeatureInformationalAPIAction required: No
AI Benchmark Repository — foundation (schema, REST API, outlier detection)
The gotcontext.ai benchmark repository is now open for submissions. Log your inference runs — model, quant, hardware, context length, batch size, tokens/sec — and get a shareable permalink. Anonymous submissions are accepted alongside authenticated ones.
- Fixed in:
- API v1.25.0
- Components:
- POST /v1/benchmarks/runs · GET /v1/benchmarks/runs/:id_or_slug · GET /v1/benchmarks/leaderboard · POST /v1/benchmarks/runs/:id/flag · GET /v1/benchmarks/hardware
Show details ▾Hide details ▴
- Six REST endpoints: submit a run, fetch by ID or public slug, leaderboard with 4 ranking lenses, community flag (≥3 flags = flagged status), hardware SKU autocomplete.
- Outlier detection runs on every submission: rolling p50 ± σ per (model, hardware) pair; outlier_score flags results that are >2σ from the median so you can investigate unusual numbers before sharing.
- Anonymous submissions are accepted with rate limiting (5/hour per IP); authenticated gc_ key submissions get a higher limit (20/hour). Both receive the same permalink and outlier scoring.
Bug fixMediumAPIAction required: No
Billing accuracy sweep — model-aware pricing across all savings surfaces
Savings figures are now consistent and model-accurate across every surface: the weekly digest email, the usage alert suffix in `/v1/usage`, and the per-compression `estimated_cost_saved` field. Customers running Claude Opus were seeing savings understated by up to 5×; Haiku users were seeing savings overstated by up to 3.75×. No customer action required — the fix applies to the next weekly digest and any new compressions.
- Fixed in:
- API v1.23.20
- Components:
- weekly digest email · /v1/usage alert suffix · /v1/compress estimated_cost_saved · Pro pricing copy in two email templates
Show details ▾Hide details ▴
- Weekly digest email now derives savings from the most-common model in your 7-day window (same formula as the `/v1/usage/by-model` endpoint), replacing a hardcoded $3/M flat rate.
- /v1/usage alert suffix now uses the blended-fallback rate ($5/M) instead of always assuming Opus pricing, which was over-promising savings for non-Opus users.
- /v1/compress `estimated_cost_saved` now delegates to the unified model pricing catalog; unknown models receive the blended fallback instead of null.
- Two email templates updated from the outdated $29/mo Pro price to the current $49/mo.
FeatureInformationalMCP GatewayAction required: No
MCP gateway — ?profile=core (7 tools, ~2K tokens) vs ?profile=full (142 tools)
Clients that only need compression, search, and memory can now connect to `https://api.gotcontext.ai/mcp?profile=core` and receive exactly 7 essential tools using ~2K tokens instead of ~38K. The default unparameterized URL is unchanged and continues to advertise the full 142-tool catalog.
- Fixed in:
- API v1.23.18
- Components:
- MCP Gateway · Claude Code plugin default config
Show details ▾Hide details ▴
- `?profile=core` returns 7 tools: ingest_context, read_skeleton, search_semantic, modulate_region, get_compression_presets, set_compression_profile, get_compression_profile.
- The Claude Code plugin bundle now defaults to `?profile=core` for new installs, reducing context overhead. Existing installs continue to use the full URL unchanged.
- Both profile endpoints run concurrently with separate session managers — a core-profile session failure logs a warning but does not affect the full-profile endpoint.
ImprovementMediumMCP GatewayAPIAction required: No
MCP gateway hardening — schema accuracy, SSRF policy errors, and 142-tool coverage sweep
Three reliability improvements to the MCP gateway: the `gc_blast_radius` tool now correctly communicates that `focus_symbol` is required (previously the schema said optional but the underlying tool required it, causing confusing errors); SSRF-blocked requests now return a structured JSON error instead of a bare network message; a 142-tool end-to-end sweep confirmed every tool works as documented.
- Affected:
- MCP clients using `gc_blast_radius` without `focus_symbol` prior to API v1.23.15
- Fixed in:
- API v1.23.15
- Components:
- MCP `gc_blast_radius` tool · MCP `proxy_mcp_server` tool · MCP `check_budget` tool · CI deploy pipeline
Show details ▾Hide details ▴
- `gc_blast_radius`: `focus_symbol` is now correctly marked required in the tool schema. Agents that auto-construct calls from `tools/list` will receive a clear validation error instead of a wrapped degraded response when the field is omitted.
- `proxy_mcp_server`: SSRF-blocked requests (localhost, 169.254.x, RFC1918 ranges) now return `{"error": "ssrf_policy_violation", "reason": "...", "host": "..."}` so agents can distinguish policy blocks from transient network failures.
- `check_budget`: the schema now rejects unknown fields with a validation error rather than silently ignoring them, so agents calling with unsupported parameters get immediate feedback.
- A 142-tool end-to-end sweep confirmed all tools are reachable and return structured responses. The sweep runs automatically against production after every deploy.
FeatureInformationalMCP GatewayAction required: No
Four new MCP tools for code context — agent capsule, edit plan, callers, context render
Four new Pro-tier MCP tools give agents structured context before making code changes: `gc_agent_capsule` summarises what to change and validates it, `gc_edit_plan` produces a machine-readable edit plan, `gc_callers` finds call sites for a symbol, and `gc_context_render` returns a ranked prompt-ready context bundle for a natural-language query.
- Fixed in:
- API v1.23.9
- Components:
- MCP `gc_agent_capsule` (new) · MCP `gc_edit_plan` (new) · MCP `gc_callers` (new) · MCP `gc_context_render` (new) · MCP `gc_blast_radius` (extended)
Show details ▾Hide details ▴
- `gc_agent_capsule`: call before any non-trivial code change. Returns primary targets, code snippets, validation commands, rollback metadata, and a confidence score.
- `gc_edit_plan`: returns a machine-readable plan listing which files to modify and what to add or remove, alongside validation commands.
- `gc_callers`: find every call site for a symbol and the test files most likely to be affected — lighter weight than a full blast-radius analysis.
- `gc_blast_radius` gains 5 new optional parameters: render_profile (full / compact / llm), optimize_context, max_depth, max_files, max_render_chars. The `llm` render profile is designed for direct LLM consumption.
Bug fixHighMCP GatewayAPIAction required: No
`gc_blast_radius` restored — tool was returning degraded responses for most callers
The `gc_blast_radius` MCP tool was returning a degraded error response for any caller who did not pass `focus_symbol` (which the schema incorrectly marked optional). This affected the majority of real-world uses. The tool now returns a clear, actionable error when `focus_symbol` is missing. Callers who were passing it correctly were unaffected.
- Affected:
- API v1.23.0 – v1.23.6 (gc_blast_radius callers omitting focus_symbol)
- Fixed in:
- API v1.23.8
- Components:
- MCP `gc_blast_radius` tool
Show details ▾Hide details ▴
- Root cause: an upstream CLI version change made `focus_symbol` required, but the tool schema still advertised it as optional. Calls without the field received an opaque `degraded=True` response rather than a useful error message.
- Fix: the service now validates `focus_symbol` before calling the CLI and returns a clear message ("focus_symbol is required") at the MCP protocol level. The schema was updated to match.
- Knowledge Hub audit-trail fidelity was also improved in this window: API key short IDs are now captured alongside user IDs in KB write operations so audit logs remain complete for gc_-key-authenticated sessions.
Bug fixMediumWebAction required: No
French locale landing page and sign-up now render in French; billing checkout path fixed
The French-language site (`/fr/`) was displaying English copy despite `<html lang="fr">` being set. Every major landing page section (Hero, Navbar, Features, FAQ, CTA, Footer) and the sign-up page now render in French. Separately, the pricing page CTA (`/sign-up?plan=pro`) now correctly routes new users through sign-up into the Polar checkout — the `plan` parameter was previously dropped silently.
- Fixed in:
- API v1.23.6
- Components:
- /fr landing page · /fr/sign-up · /dashboard/billing upgrade CTA
Show details ▾Hide details ▴
- French translations wired across all LandingPage sections. Brand terms (MCP, gotcontext.ai, API key) kept in English; B2B copy uses formal "vous".
- The sign-up page now forwards the `plan` query parameter through Clerk's redirect flow so users land on the billing page with an active checkout prompt.
- Billing page: the `?upgraded=1&plan=pro` redirect from Clerk now shows a "Continue to Pro checkout" CTA that posts directly to the Polar checkout URL, closing the activation gap.
Bug fixHighAPIAction required: No
Agent payment settlement status now flips correctly after payment verification
When an agent paid for a compression call via Nevermined or Skyfire, the payment was processed but the settlement status remained stuck at "pending" permanently. The reconciliation API now reflects settled payments correctly. This is an infrastructure fix; no end-user action is required.
- Fixed in:
- API v1.23.2
- Components:
- agent payment middleware · GET /v1/payments/reconciliation · /v1/compress (agent-payment-authenticated calls)
Show details ▾Hide details ▴
- Root cause: the settlement updater looked up the usage event by a row ID that was generated asynchronously and was never actually set in the request context.
- Fix: the settlement updater now looks up the pending payment by (payment_id, facilitator) with a 3-attempt retry, giving the async write time to land before the update fires.
- Observability: 7 Sentry breadcrumbs added to the payment middleware covering verification, settlement scheduling, and retry exhaustion — with vendor and amount tracked (no wallet addresses or signing material).
InternalInformationalAPIWebAction required: No
Public changelog now auto-synced: CI hard-fails when the page drifts behind CHANGELOG.md
A new CI gate ensures the customer-facing changelog at /changelog is updated with every release. The gate allows exactly one in-flight version (the time between writing a CHANGELOG entry and deploying the page update), but fails the deploy if two or more releases are missing. This release also backfills the public page with 10 entries covering API v1.23.2 through v1.27.0 that were missing.
- Fixed in:
- API v1.27.1
- Components:
- /changelog · CI deploy pipeline (deploy-staging, deploy-production)
Show details ▾Hide details ▴
- New script `scripts/check_public_changelog_sync.py` compares versions in CHANGELOG.md against version strings in the /changelog page and exits 1 when 2+ are missing.
- New `public-changelog-sync` CI job runs in parallel with the test job and gates both deploy-staging and deploy-production.
- Backfill: 10 customer-facing entries added covering all versions from API v1.23.2 to v1.27.0 that were previously missing from the public page.
Bug fixMediumAPIWebAction required: No
Analytics savings figures now match the dashboard's top-keys widget
The per-project analytics summary was showing savings figures that did not match the "Top expensive keys" widget on the main dashboard. The two surfaces were using different formulas and different time windows. Both now use the same model-aware pricing and a consistent 30-day window by default.
- Fixed in:
- API v1.23.19
- Components:
- GET /v1/analytics/summary · /dashboard/analytics per-project savings · /dashboard Top expensive keys widget
Show details ▾Hide details ▴
- Analytics savings now use the same model-aware formula as the top-keys widget: the most common model in the selected window determines the per-token rate, falling back to the blended average for mixed-model usage.
- A `days` query parameter was added to `/v1/analytics/summary` (1–365). The dashboard now passes `days=30` by default, matching the top-keys widget window. Omitting the parameter returns the lifetime aggregate (backward compatible).
- An inline "Last 30 days" badge and tooltip were added to the analytics page so users know what window is displayed.
ImprovementInformationalMCP GatewayAPIAction required: No
MCP tool schema honesty improvements and 142-tool end-to-end sweep CI gate
The `check_budget` tool now returns a validation error when called with unknown fields (it was previously silently ignoring them). Knowledge Hub MCP tools now explicitly document the requirement for a project-bound API key in their descriptions. A CI job runs the full 142-tool end-to-end sweep against live production after every deploy.
- Fixed in:
- API v1.23.17
- Components:
- MCP `check_budget` tool · MCP `gc_kb_*` tools (7 tools) · CI deploy pipeline
Show details ▾Hide details ▴
- `check_budget`: the schema now uses `"additionalProperties": false` — calling the tool with an unrecognized field like `{"period": "quarterly"}` now returns an error instead of silently returning success without applying the filter.
- All 7 Knowledge Hub MCP tools now state in their descriptions that they require a project-bound `gc_` API key. Previously this was an opaque runtime surprise.
- A 142-tool end-to-end sweep CI job was added that runs the full tool suite against live production after every deploy, catching regressions that smoke tests miss.
InternalInformationalAPIAction required: No
Internal: CI test fix for Knowledge Hub SQLite compatibility — no customer-visible change
Three consecutive releases were blocked from deploying to production by a CI test failure in the Knowledge Hub test suite. Fixed by correctly skipping Postgres-specific tests when CI runs against SQLite. No customer-visible change.
- Fixed in:
- API v1.23.10
- Components:
- CI test suite
Show details ▾Hide details ▴
- Knowledge Hub tests that require Postgres-specific schema now correctly skip in the SQLite CI environment and continue to run in the integration environment against real Postgres.
ImprovementInformationalWebAction required: No
Quality sprint — dashboard accessibility, pricing rewrite, blog standardization
Four dashboard surfaces (Webhooks, Teams, Profiles, Admin) audited to 3/3 convergence for accessibility, focus management, and error semantics. The /pricing page was rewritten in two waves (ROI calculator, SLA matrix, sub-processors list, trust portal); a separate fix corrected the contact form rendering raw HTML. Every blog post now uses the same canonical layout (TOC, cite block, related links, contribute grid) and was scrubbed against an anti-AI-writing rubric.
- Fixed in:
- Web v1.24.1
- Components:
- /dashboard/webhooks · /dashboard/teams · /dashboard/profiles · /dashboard/admin · /pricing · /docs · /blog
Show details ▾Hide details ▴
- Dashboard accessibility convergence: modal focus traps via useRef, Escape handlers, error banners marked aria-live, icon-button focus-visible rings, DeleteConfirmDialog focus management, dialog aria-describedby. ~70 audit findings closed across the four surfaces over two weeks.
- /pricing rewrite landed in two waves: brutal-audit cleanup of unsubstantiated claims followed by an interactive ROI calculator, SLA matrix, sub-processors list, and trust-portal link. A subsequent hotfix corrected the contact form which was rendering raw HTML.
- /docs fixes: demo Run button 422 (default sample was exceeding the 5000-char cap); unstyled paragraphs and inline code in the Output Style and Sensitive-Content sections.
- Blog standardization: every post (26 dynamic markdown posts plus the standalone methodology post) now renders through a single BlogPostLayout component with auto-generated TOC, cite block, related-links sidebar, and contribute grid. An anti-AI-writing skill was added with Tier-1 lexical bans + em-dash density cap; existing content was swept (153 em-dashes reduced to 9 across the blog).
- Top navigation: the "Product" link in the global nav was using a bare `#platform` fragment, which only worked from the landing page. Repaired to `/#platform` so the link routes correctly from any page. Same fix applied to the footer Features link.
- Test suite: full vitest suite restored to green (23 failing tests fixed, 368/368 passing). Backend fidelity enum mismatch between API and dashboard surfaced and corrected during the /dashboard/profiles audit.
FeatureInformationalAPIMCP GatewayAction required: No
Onboarding email drip + free-tier MCP context lookup tool
New users receive a 5-email onboarding drip over 14 days via Resend (feature-flag-gated against the existing welcome path so existing users are unaffected). Free-tier MCP keys can now see `gc_lookup` in `tools/list` (returns 501 stub pending the Phase 2 ingestion pipeline; behaviour will change to a working response without a version bump when Phase 2 ships).
- Fixed in:
- API v1.23.0
- Components:
- Resend integration · MCP `tools/list` · free-tier gating
Show details ▾Hide details ▴
- Onboarding drip uses Resend `scheduled_at` for the 14-day cadence. The existing welcome email path is unaffected.
- `gc_lookup` MCP tool surfaces in `tools/list` for free-tier keys. The endpoint currently returns 501 Not Implemented while the ingestion pipeline is in development. The contract will become live without a version bump (so MCP clients that already enumerate `gc_lookup` will start receiving real responses transparently).
FeatureInformationalWebAction required: No
AI news feed and framework context hub
Two new pages on gotcontext.ai for AI context-engineering reference: `/news` (chronological feed of curated AI items across Models, Funding, Tools, Papers, Infrastructure, Agents, Policy) and `/context` (framework hub covering 9 frameworks: Next.js, FastAPI, LangChain, SQLAlchemy, Pydantic, Tailwind, Drizzle, FastMCP, React).
- Fixed in:
- Web v1.24.0
- Components:
- /news · /context
Show details ▾Hide details ▴
- `/news` ships with 28 hand-curated AI items at launch and is indexed in the global Cmd+K command palette.
- `/context` framework pages mirror the same Cmd+K integration so each framework is reachable directly from the keyboard shortcut.
Bug fixMediumAPIAction required: No
Budget alert delivery restored for users who re-enabled email alerts
Users who opted out of email alerts and then later re-enabled them were not receiving the 75 / 90 / 100 % project-budget threshold notifications. Fixed in API v1.22.8. No customer action required — the next budget-check tick after the upgrade will deliver the still-relevant alert.
- Fixed in:
- API v1.22.8
- Components:
- project budget cron · Resend send branch
Show details ▾Hide details ▴
- Root cause: the budget-check cron's send branch advanced `last_alerted_threshold` for opted-out users in the same step as missing-email users. A user who re-opted-in after crossing 75 % would never receive an alert because the threshold counter had already moved past 75 / 90 / 100.
- Fix: opt-out now leaves the `last_alerted_threshold` field untouched, so re-opt-in delivers the still-relevant alert at the next cron tick.
Bug fixHighAPIWebAction required: No
GET /v1/projects/{id}/usage returned 500
The per-project usage endpoint had been returning HTTP 500 in production. The dashboard's per-project usage tab and any direct API integration relying on this endpoint were affected. Fixed in API v1.22.7.
- Fixed in:
- API v1.22.7
- Components:
- /v1/projects/{id}/usage · per-project usage tab
Show details ▾Hide details ▴
- Postgres rejected the per-model breakdown query because asyncpg renders identical `coalesce()` defaults as separate parameter bindings, producing a duplicate-parameter error at execution time.
- Fix: GROUP BY the raw column and apply the `"blended-default"` fallback in Python after the query returns.
ImprovementLowAPIWebAction required: No
is_default field exposed on /v1/projects responses
The default-project marker is now returned by the API so client code can drive UI gating from server truth instead of inferring it from a project name. No breaking changes — additive field.
- Fixed in:
- API v1.22.7
- Components:
- /v1/projects · dashboard project picker
Show details ▾Hide details ▴
- The dashboard's Delete-button gate previously inferred which project was the default by matching `name === "Default"` — a fragile string match.
- API responses now include `is_default: boolean`. The dashboard reads this directly. Custom API integrations can also consume the field.
Bug fixMediumMCP GatewayAction required: No
Removed 5 unimplemented MCP tool stubs from tools/list
Five MCP tools that appeared in `tools/list` had no implementation behind them — calling them returned an error. They have been removed from the listing until the implementations land. Clients that enumerated and called these tools will see them stop appearing in `tools/list`; calls to them would have failed previously anyway.
- Fixed in:
- API v1.22.3
- Components:
- MCP `tools/list`
Show details ▾Hide details ▴
- The five tools were registered in the schema but no dispatch handler existed. The bug class is now locked by `api/tests/test_mcp_gateway.py` — every registered tool must have both a schema entry and a dispatch handler, or CI fails.
ImprovementLowWebAction required: No
Usage badge now shown on each project in Dashboard
Each project card in the dashboard now displays a usage badge showing this month's API-call count, so users can see at a glance which projects are active without opening each one. No customer action required.
- Fixed in:
- Web v1.23.4
- Components:
- /dashboard/projects
Show details ▾Hide details ▴
- The badge reads from the same per-project rollup the dedicated usage tab uses, so the values match end-to-end.
InternalInformationalAPIAction required: No
Internal: per-project FinOps surface hardening — no customer-visible change
Internal cleanup across the per-project FinOps surface (project budgets, per-project usage, project-bound API keys). No customer-visible API contract change. Bundled here for SemVer traceability.
- Fixed in:
- API v1.22.5
- Components:
- per-project FinOps internals
Show details ▾Hide details ▴
- Code-quality, internal-test, and refactor work. If any change affects the public API contract, it appears as its own first-class entry above.
InternalInformationalWebAction required: No
Internal: dashboard FinOps audit — no customer-visible change
Internal-only audit pass on the dashboard FinOps surface. No customer-visible UI or behavioural change. Bundled here for SemVer traceability.
- Fixed in:
- Web v1.23.3
- Components:
- dashboard FinOps internals