Skip to content

feat: sentry DSN wiring + metrics shim audit (dead set() removed, 11 new tests)#58

Merged
YiWang24 merged 15 commits into
mainfrom
feat/observability-and-infra-updates
May 20, 2026
Merged

feat: sentry DSN wiring + metrics shim audit (dead set() removed, 11 new tests)#58
YiWang24 merged 15 commits into
mainfrom
feat/observability-and-infra-updates

Conversation

@YiWang24
Copy link
Copy Markdown
Collaborator

@YiWang24 YiWang24 commented May 19, 2026

Summary

  • Wires live Sentry DSNOPENBOT_SENTRY_DSN is now read by Settings and passed to sentry_sdk.init() in both the API and worker entrypoints. Set it in .env (see instructions below).
  • Removes dead Metrics.set()sentry_sdk.metrics has no set function in 2.x; the guarded hasattr check was silently dropping all calls. Replaced with a docstring note pointing callers to distribution().
  • Adds 11 new tests — covers the metrics shim (happy-path delegation + 3 resilience cases) and augments test_obs.py with profiles_sample_rate assertion + sentry_initialised log-line test.

Files changed

File Change
openbot/core/sentry_metrics.py Remove Metrics.set(), fix module docstring
tests/core/__init__.py New — package marker for openbot/core/ mirror
tests/core/test_sentry_metrics.py New — 7 tests for the metrics shim
tests/infrastructure/test_obs.py +2 assertions (profiles_sample_rate, log line)
docs/superpowers/specs/… Design spec
docs/superpowers/plans/… Implementation plan

Setting the DSN (manual step — not committed)

.env files are gitignored and Semgrep blocks AI writes to them. Run once:

echo 'OPENBOT_SENTRY_DSN=https://f07a401fd38b70db0359272557c974d6@o4511407339012096.ingest.us.sentry.io/4511407339143168' >> .env

Verify:

uv run python -c "
from openbot.core.settings import get_settings; get_settings.cache_clear()
s = get_settings()
print('DSN set:', s.sentry_dsn is not None)
"

For Heroku production:

heroku config:set OPENBOT_SENTRY_DSN=https://f07a401fd38b70db0359272557c974d6@o4511407339012096.ingest.us.sentry.io/4511407339143168

Test plan

  • uv run pytest tests/core/test_sentry_metrics.py -v — 7 passed
  • uv run pytest tests/infrastructure/test_obs.py -v — 4 passed
  • make check — 697 passed, ruff + import-linter clean
  • Manual: add DSN to .env, run make dev, confirm sentry_initialised log line at startup

View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Summary by CodeRabbit

Release Notes

  • New Features

    • Added /ready health check endpoint with component status reporting
    • Integrated continuous Sentry profiling and logging
    • Added database initialization command (make db-init)
    • Enhanced chat with DeepAgents integration
    • Added request ID tracking for improved webhook debugging
  • Infrastructure & Operations

    • Added Docker support with Dockerfile and docker-compose configuration
    • Implemented structured JSON logging for better observability
    • Added Heroku monitoring bootstrap automation
    • Enhanced metrics collection for workflows and queue depth
  • Documentation

    • Updated README with database initialization steps
    • Expanded Heroku deployment runbook with monitoring guidance

Review Change Stack


Sentry Logs (added in follow-up commit)

  • Bumped sentry-sdk[fastapi]>=2.35.0 (Logs feature requires 2.35+)
  • Added enable_logs=True to sentry_sdk.init()
  • Added explicit LoggingIntegration(level=WARNING, event_level=ERROR) — keeps INFO lines (e.g. http_request_completed) out of Sentry Logs while forwarding warnings and errors
  • Test: asserts enable_logs=True and LoggingIntegration presence in the existing obs test suite

Sentry Continuous Profiling (added in follow-up commit)

  • Replaced profiles_sample_rate (transaction profiling) with profile_session_sample_rate=1.0 (continuous profiling)
  • sentry_sdk.profiler.start_profiler() called in webapp lifespan startup and worker _main after init_sentry()
  • stop_profiler() called in webapp lifespan finally block; worker relies on SDK atexit flush
  • Both calls wrapped in try/except Exception: pass — graceful degradation when SDK absent or DSN unset
  • Doppler prd: removed OPENBOT_SENTRY_PROFILES_SAMPLE_RATE, added OPENBOT_SENTRY_PROFILE_SESSION_SAMPLE_RATE=1.0

@yibots
Copy link
Copy Markdown

yibots Bot commented May 19, 2026

🤖 OpenBot received this PR and review is queued.

v0.1 alpha: only the ACK is automated so far. Full review (diff scan, structured findings, severity filter) lands in an upcoming commit.

@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

Introduces domain enums and metrics, expands ports and GitHub adapter, adds ingest_webhook orchestration, refactors the webhook route to use a dependency and use case, enhances observability (Sentry logs/profiling, JSON logging, LangSmith), adds Docker/dev tooling, DB init CLI, and updates tests/docs accordingly.

Changes

Webhook ingestion and domain/ports reshaping

Layer / File(s) Summary
Contracts→Adapters→Use cases→Entrypoints→Infra/Dev wiring→Tests
openbot/domain/*, openbot/application/ports/*, openbot/infrastructure/adapters/github.py, openbot/application/use_cases/*, openbot/entrypoints/api/*, openbot/infrastructure/*, openbot/core/*, Dockerfile, docker-compose.yml, .github/workflows/ci.yml, .pre-commit-config.yaml, .importlinter, Makefile, scripts/*, tests/**/*, docs/**/*, pyproject.toml
Domain enums consolidated; ports widened; adapter gains label/comments/file/role helpers; new ingest_webhook orchestrates dedup, classification, enqueue; API route becomes thin; observability upgraded (Sentry logs/profiling, LangSmith, JSON logs); DB init CLI added; dev/CI/docker updated; comprehensive tests/docs added.

Sequence Diagram(s)

sequenceDiagram
  participant Client as GitHub
  participant API as FastAPI App
  participant Dep as verified_github_event
  participant UseCase as ingest_webhook
  participant Adapter as GitHubAdapter
  participant Queue as Redis Stream + Worker

  Client->>API: POST /webhook/github
  API->>Dep: Read body + headers
  Dep->>Adapter: verify_signature + parse_event
  Dep-->>API: UnifiedEvent
  API->>UseCase: event + ports (dedup/runs/lock/cancel/queue)
  UseCase->>Adapter: optional reads (labels/comments/file) / check-run update
  UseCase->>Queue: enqueue(event, feature, task_id,…)
  Queue-->>UseCase: entry_id
  UseCase-->>API: accepted/ignored/duplicate + ids
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • YiAgent/openbot#14 — Moves triage workflow into use_cases and adopts domain Feature/WorkflowPhase enums.
  • YiAgent/openbot#52 — Introduces/refines ports layer; related to dispatcher signature and protocol changes.

Poem

A rabbit taps the queue with gentle paws,
Domains aligned, adapters learn new laws.
Webhooks hop in, use cases weave the thread,
Sentry blinks awake, JSON logs are spread.
Containers purr, DB seeds on a whim—
Review me now; let workflows brim. 🐇✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/observability-and-infra-updates

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

268-270: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove accidental trailing text from README.

Line 268–Line 270 look like leftover scratch text and make the documentation appear broken to readers.

Proposed fix
-# E2E Test
-# Fixed
-Final Test
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 268 - 270, Remove the accidental trailing scratch
text at the end of README by deleting the stray lines containing "E2E Test",
"Fixed", and "Final Test" so the file ends cleanly; ensure no leftover headings
or placeholder text remain and that the README content ends with the intended
final section or newline.
🧹 Nitpick comments (2)
obser.txt (1)

1-1: ⚡ Quick win

Remove stray artifact file before merge.

Line 1 appears to be a temporary test artifact and adds noise to the repo history.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@obser.txt` at line 1, Remove the stray temporary artifact by deleting the
file obser.txt (or removing its single-line content "obser test") before
merging; ensure no other commits reintroduce this test artifact and update the
branch so the repository history no longer contains this noisy temporary file.
tests/application/use_cases/test_triage.py (1)

116-117: ⚡ Quick win

Align remaining caplog logger names with use_cases.triage.

Three tests still target openbot.application.workflows.triage; after the move, this makes log-level scoping brittle and inconsistent.

Suggested patch
-    with caplog.at_level(logging.WARNING, logger="openbot.application.workflows.triage"):
+    with caplog.at_level(logging.WARNING, logger="openbot.application.use_cases.triage"):
@@
-    with caplog.at_level(logging.WARNING, logger="openbot.application.workflows.triage"):
+    with caplog.at_level(logging.WARNING, logger="openbot.application.use_cases.triage"):
@@
-    with caplog.at_level(logging.ERROR, logger="openbot.application.workflows.triage"):
+    with caplog.at_level(logging.ERROR, logger="openbot.application.use_cases.triage"):

Also applies to: 126-127, 177-178

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/application/use_cases/test_triage.py` around lines 116 - 117, Update
the caplog logger names used when setting log level in the triage tests: replace
occurrences of "openbot.application.workflows.triage" with the new module path
"openbot.application.use_cases.triage" where caplog.at_level is used (e.g., the
test blocks that call caplog.at_level(logging.WARNING,
logger="openbot.application.workflows.triage") around
maybe_run_triage(_ctx(_event(...))) and similar calls); ensure all three
remaining instances (the blocks wrapping maybe_run_triage and related
assertions) use "openbot.application.use_cases.triage" so log-level scoping
matches the moved module.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.example:
- Line 26: The commit modifies the `.env.example` entry for
CLAUDE_SWITCH_GLM_BASE_URL which violates the rule disallowing committed .env*
files; either revert the change to keep `.env.example` untouched or move the
template line for CLAUDE_SWITCH_GLM_BASE_URL into a non-.env filename (e.g.,
rename to env.example.template or config/claude-proxy.template) and update
references accordingly, and if you truly intend to commit this template, update
the policy/ignore rules to explicitly allow `env.example.template` (not
`.env*`), ensuring no `.env*` file is added to the repo or commit history.

In `@Dockerfile`:
- Around line 1-14: The Dockerfile currently runs as root; create and switch to
a non-root user by adding a user/group (e.g., 1000:1000 or a named user like
"appuser"), chown the application files and workdir to that user after COPY, and
add a USER instruction before CMD so the container drops privileges at runtime;
update any ENV (e.g., HOME) if needed and ensure commands that require root
(like package installs) remain before switching. Reference the Dockerfile's
WORKDIR, COPY, RUN uv sync --frozen --no-dev, and CMD ["uv", "run", ...] when
placing the user creation, chown, and USER directives.

In `@docs/superpowers/plans/2026-05-19-sentry-dsn-metrics-audit.md`:
- Around line 1-2: The file titled "Sentry DSN + Metrics Audit Implementation
Plan" is in the wrong location; move its content into the repository's
single-context docs layout by converting the plan into an ADR-style entry and
placing it under the repository's docs/adr directory, then update the root
CONTEXT.md to reference the new ADR (or remove the stray plan file); ensure the
ADR uses the same title and metadata so reviewers can find it and delete the
original plan file from docs/superpowers/plans to avoid duplication.
- Around line 40-41: Replace the real Sentry DSN value currently assigned to the
OPENBOT_SENTRY_DSN example with a non-sensitive placeholder string; locate the
OPENBOT_SENTRY_DSN line in the docs and change the value to a generic example
(e.g. a clearly marked placeholder like
"https://<PUBLIC_KEY>@<HOST>/<PROJECT_ID>" or "your_sentry_dsn_here") so no live
DSN is committed.

In `@docs/superpowers/specs/2026-05-19-sentry-dsn-metrics-audit-design.md`:
- Around line 60-62: Replace the hard-coded Sentry DSN example string for
OPENBOT_SENTRY_DSN with a sanitized placeholder; locate the OPENBOT_SENTRY_DSN
example entry and swap the live DSN value (the long URL starting with
https://...ingest...) for a non-functional placeholder such as
https://<PUBLIC_KEY>`@sentry.io/`<PROJECT_ID> or REDACTED_SENTRY_DSN so the spec
no longer contains real credentials.

In `@openbot/application/middleware/security.py`:
- Around line 226-228: When calling the delegated adapter methods (specifically
ctx.adapter.get_pr_comments invoked where ctx.cache[cache_key] = result and the
nearby call around line 232), wrap the call in a try/except that catches adapter
exceptions and on failure logs the error and returns a safe default (empty list
for comments or "none" for role checks) instead of letting the exception
propagate; also ensure you populate ctx.cache[cache_key] with the default value
so the gate remains fail-closed on adapter failures.

In `@openbot/application/ports/channel_adapter.py`:
- Around line 54-56: The current signature in
openbot.application.ports.channel_adapter where parameters status and conclusion
default to status="completed" and conclusion=None violates GitHub Checks API;
change the default status to a non-completed state (e.g., "in_progress" or
"queued") and keep conclusion optional, and also add runtime validation inside
the same function (referencing the status and conclusion parameters) to
raise/return a clear error if status == "completed" but conclusion is still None
so callers get immediate feedback; update the function/type hints and any
docstring for the function in channel_adapter.py to reflect the corrected
contract.

In `@openbot/core/metrics.py`:
- Around line 93-107: The WorkflowCounter and CostCounter currently mutate
self._kwargs in labels(), causing shared singleton state bugs; change labels()
to return a new immutable bound handle object (e.g., WorkflowCounterBound /
CostCounterBound) that stores the provided kwargs internally and whose
inc(amount) uses that stored kwargs to call
_workflow_total.labels(**kwargs).inc(amount) and metrics.incr("workflow_total",
amount, tags=kwargs); do not write to the singleton's attributes. Apply the same
refactor to the other singleton metric wrappers (the other classes using
labels()/inc()) so labels() returns a bound instance instead of mutating the
singleton.

In `@openbot/infrastructure/adapters/github.py`:
- Line 421: Replace the lax Base64 decode call so invalid payloads fail fast:
change the call using base64.b64decode(encoded, validate=False) to use
validate=True (i.e., base64.b64decode(encoded, validate=True)) in the repository
file decoding function where the variable encoded is decoded; also add handling
for binascii.Error around that call to raise or propagate a clear decoding error
(so callers can surface a useful message) and ensure binascii is imported if
needed.

In `@openbot/infrastructure/agents/deepagents_chat.py`:
- Around line 75-83: The _extract_reply function assumes messages[-1] is an
object with a .content attribute and drops valid replies when the final message
is a dict; update _extract_reply to handle both dict and object shapes by
retrieving content via messages[-1].get("content") if isinstance(messages[-1],
dict) otherwise getattr(messages[-1], "content", None), then pass that content
to _extract_message_text to avoid spurious deepagents_result_missing_text errors
(refer to _extract_reply and messages[-1]).

In `@openbot/infrastructure/queue/worker.py`:
- Around line 93-109: The Sentry tags are currently set globally which can leak
between concurrent consumers; update _attach_sentry_tags to create an explicit
per-dispatch Sentry scope and set tags inside it (use sentry_sdk.push_scope() as
a context manager, or sentry_sdk.isolation_scope() if available), keeping the
existing try/except so missing sentry_sdk is still safe, and ensure the
dispatch/handler that currently calls _attach_sentry_tags invokes it within that
scope (or let _attach_sentry_tags itself manage the scope) so tags (delivery_id,
feature, repo) are isolated per job and cannot bleed across asyncio tasks.

In `@tests/infrastructure/agents/test_deepagents_chat.py`:
- Around line 47-51: The test currently asserts exact prompt wording (checks for
"GitHub maintainer bot" in seen["kwargs"]["system_prompt"] and several
substrings in prompt = seen["payload"]["messages"][0]["content"] like
"YiAgent/openbot", "issue `#42`", "summarize this"); remove those literal
prompt-content assertions from
tests/infrastructure/agents/test_deepagents_chat.py and instead assert only
deterministic contract properties (e.g., that seen contains kwargs.system_prompt
and payload.messages is a non-empty list or has expected message count/type)
using the existing variables seen, system_prompt, payload and messages; move the
exact prompt-quality checks into an eval under evals/ that verifies the prompt
wording and intent.

In `@tests/infrastructure/test_config_blank_coercion.py`:
- Around line 53-54: The test asserts the default for
Settings().debug_echo_enabled but can be influenced by the environment var
OPENBOT_DEBUG_ECHO_ENABLED; modify the test to ensure environment isolation by
clearing or unsetting OPENBOT_DEBUG_ECHO_ENABLED before instantiating Settings
(e.g., using monkeypatch.delenv or os.environ.pop) so Settings() reads a clean
environment, then assert debug_echo_enabled is False; ensure the env change
applies only for the test scope so other tests aren’t affected.

---

Outside diff comments:
In `@README.md`:
- Around line 268-270: Remove the accidental trailing scratch text at the end of
README by deleting the stray lines containing "E2E Test", "Fixed", and "Final
Test" so the file ends cleanly; ensure no leftover headings or placeholder text
remain and that the README content ends with the intended final section or
newline.

---

Nitpick comments:
In `@obser.txt`:
- Line 1: Remove the stray temporary artifact by deleting the file obser.txt (or
removing its single-line content "obser test") before merging; ensure no other
commits reintroduce this test artifact and update the branch so the repository
history no longer contains this noisy temporary file.

In `@tests/application/use_cases/test_triage.py`:
- Around line 116-117: Update the caplog logger names used when setting log
level in the triage tests: replace occurrences of
"openbot.application.workflows.triage" with the new module path
"openbot.application.use_cases.triage" where caplog.at_level is used (e.g., the
test blocks that call caplog.at_level(logging.WARNING,
logger="openbot.application.workflows.triage") around
maybe_run_triage(_ctx(_event(...))) and similar calls); ensure all three
remaining instances (the blocks wrapping maybe_run_triage and related
assertions) use "openbot.application.use_cases.triage" so log-level scoping
matches the moved module.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 59098e62-67e7-4d45-95b1-4a29cf317663

📥 Commits

Reviewing files that changed from the base of the PR and between 249189c and 76f0766.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (95)
  • .dockerignore
  • .env.example
  • .github/workflows/ci.yml
  • .gitignore
  • .importlinter
  • .pre-commit-config.yaml
  • Dockerfile
  • Makefile
  • README.md
  • app.json
  • docker-compose.yml
  • docs/deploy/heroku.md
  • docs/superpowers/plans/2026-05-19-sentry-dsn-metrics-audit.md
  • docs/superpowers/specs/2026-05-19-sentry-dsn-metrics-audit-design.md
  • obser.txt
  • openbot/application/dispatcher.py
  • openbot/application/handlers/__init__.py
  • openbot/application/middleware/audit.py
  • openbot/application/middleware/budget.py
  • openbot/application/middleware/cancel.py
  • openbot/application/middleware/feature_toggle.py
  • openbot/application/middleware/preflight.py
  • openbot/application/middleware/rate_limit.py
  • openbot/application/middleware/sanitize.py
  • openbot/application/middleware/security.py
  • openbot/application/ports/channel_adapter.py
  • openbot/application/ports/config_loader.py
  • openbot/application/ports/dedup.py
  • openbot/application/ports/queue.py
  • openbot/application/router.py
  • openbot/application/use_cases/__init__.py
  • openbot/application/use_cases/_lifecycle.py
  • openbot/application/use_cases/_tracing.py
  • openbot/application/use_cases/chat.py
  • openbot/application/use_cases/chat_parser.py
  • openbot/application/use_cases/debug_echo.py
  • openbot/application/use_cases/fix.py
  • openbot/application/use_cases/ingest_webhook.py
  • openbot/application/use_cases/review.py
  • openbot/application/use_cases/triage.py
  • openbot/application/workflows/__init__.py
  • openbot/core/logging.py
  • openbot/core/metrics.py
  • openbot/core/sentry_metrics.py
  • openbot/core/settings.py
  • openbot/domain/config_schema.py
  • openbot/domain/dedup.py
  • openbot/domain/workflows.py
  • openbot/entrypoints/api/app.py
  • openbot/entrypoints/api/deps.py
  • openbot/entrypoints/api/routes/github_webhook.py
  • openbot/entrypoints/api/routes/health.py
  • openbot/entrypoints/cli/db_init.py
  • openbot/entrypoints/worker/__main__.py
  • openbot/infrastructure/adapters/github.py
  • openbot/infrastructure/agents/__init__.py
  • openbot/infrastructure/agents/deepagents_chat.py
  • openbot/infrastructure/config_loader.py
  • openbot/infrastructure/llm/complete.py
  • openbot/infrastructure/llm/model_router.py
  • openbot/infrastructure/observability.py
  • openbot/infrastructure/persistence/db.py
  • openbot/infrastructure/persistence/dedup.py
  • openbot/infrastructure/persistence/models.py
  • openbot/infrastructure/queue/enqueue.py
  • openbot/infrastructure/queue/worker.py
  • pyproject.toml
  • scripts/doctor.py
  • scripts/heroku-monitoring-bootstrap.sh
  • scripts/hooks/import-linter.sh
  • tests/_fakes/channel_adapter.py
  • tests/_fakes/queue.py
  • tests/application/middleware/conftest.py
  • tests/application/middleware/test_cancel.py
  • tests/application/middleware/test_preflight.py
  • tests/application/middleware/test_security.py
  • tests/application/ports/test_queue_port_contract.py
  • tests/application/test_no_raw_user_input.py
  • tests/application/test_router.py
  • tests/application/use_cases/__init__.py
  • tests/application/use_cases/test_chat.py
  • tests/application/use_cases/test_chat_parser.py
  • tests/application/use_cases/test_triage.py
  • tests/core/__init__.py
  • tests/core/test_sentry_metrics.py
  • tests/e2e/conftest.py
  • tests/e2e/test_spec_demos.py
  • tests/entrypoints/api/test_app_boot.py
  • tests/entrypoints/api/test_ready.py
  • tests/entrypoints/api/test_webhook_endpoint.py
  • tests/entrypoints/cli/test_db_init.py
  • tests/infrastructure/agents/test_deepagents_chat.py
  • tests/infrastructure/test_config_blank_coercion.py
  • tests/infrastructure/test_config_repo.py
  • tests/infrastructure/test_obs.py
💤 Files with no reviewable changes (2)
  • openbot/application/workflows/init.py
  • openbot/application/handlers/init.py

Comment thread .env.example
# ANTHROPIC_API_KEY= # review/fix → claude-opus-4-7
# # triage/chat → claude-sonnet-4-6
# OPENAI_API_KEY= # fallback model
# CLAUDE_SWITCH_GLM_BASE_URL=https://open.bigmodel.cn/api/anthropic # GLM Anthropic Proxy
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid changing committed .env* files under current policy.

This PR modifies .env.example, which matches the prohibited .env* pattern. Please either move this template to a non-.env* filename or explicitly carve out .env.example in policy before merge.

As per coding guidelines .env*: Do not commit .env* files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example at line 26, The commit modifies the `.env.example` entry for
CLAUDE_SWITCH_GLM_BASE_URL which violates the rule disallowing committed .env*
files; either revert the change to keep `.env.example` untouched or move the
template line for CLAUDE_SWITCH_GLM_BASE_URL into a non-.env filename (e.g.,
rename to env.example.template or config/claude-proxy.template) and update
references accordingly, and if you truly intend to commit this template, update
the policy/ignore rules to explicitly allow `env.example.template` (not
`.env*`), ensuring no `.env*` file is added to the repo or commit history.

Comment thread Dockerfile
Comment on lines +1 to +14
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

WORKDIR /app

ENV PYTHONUNBUFFERED=1 \
UV_LINK_MODE=copy

COPY . .

RUN uv sync --frozen --no-dev

EXPOSE 8080

CMD ["uv", "run", "uvicorn", "openbot.entrypoints.api.app:app", "--host", "0.0.0.0", "--port", "8080"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Container runs as root; drop privileges before runtime.

Add a non-root user and switch to it before CMD.

Suggested hardening patch
 FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
 
 WORKDIR /app
 
 ENV PYTHONUNBUFFERED=1 \
     UV_LINK_MODE=copy
 
 COPY . .
 
 RUN uv sync --frozen --no-dev
+RUN addgroup --system openbot && adduser --system --ingroup openbot openbot
+RUN chown -R openbot:openbot /app
+USER openbot
 
 EXPOSE 8080
 
 CMD ["uv", "run", "uvicorn", "openbot.entrypoints.api.app:app", "--host", "0.0.0.0", "--port", "8080"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
UV_LINK_MODE=copy
COPY . .
RUN uv sync --frozen --no-dev
EXPOSE 8080
CMD ["uv", "run", "uvicorn", "openbot.entrypoints.api.app:app", "--host", "0.0.0.0", "--port", "8080"]
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
UV_LINK_MODE=copy
COPY . .
RUN uv sync --frozen --no-dev
RUN addgroup --system openbot && adduser --system --ingroup openbot openbot
RUN chown -R openbot:openbot /app
USER openbot
EXPOSE 8080
CMD ["uv", "run", "uvicorn", "openbot.entrypoints.api.app:app", "--host", "0.0.0.0", "--port", "8080"]
🧰 Tools
🪛 Trivy (0.69.3)

[error] 1-1: Image user should not be 'root'

Specify at least 1 USER command in Dockerfile with non-root user as argument

Rule: DS-0002

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 1 - 14, The Dockerfile currently runs as root;
create and switch to a non-root user by adding a user/group (e.g., 1000:1000 or
a named user like "appuser"), chown the application files and workdir to that
user after COPY, and add a USER instruction before CMD so the container drops
privileges at runtime; update any ENV (e.g., HOME) if needed and ensure commands
that require root (like package installs) remain before switching. Reference the
Dockerfile's WORKDIR, COPY, RUN uv sync --frozen --no-dev, and CMD ["uv", "run",
...] when placing the user creation, chown, and USER directives.

Comment on lines +1 to +2
# Sentry DSN + Metrics Audit Implementation Plan

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move this design/plan record into the repository’s single-context docs layout.

This new domain-planning doc is under docs/superpowers/plans/, which conflicts with the documented single-context structure (CONTEXT.md + docs/adr/).

As per coding guidelines, "Use a single-context layout for domain docs: root CONTEXT.md + docs/adr/ (lazily created — silent if absent)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-19-sentry-dsn-metrics-audit.md` around lines 1
- 2, The file titled "Sentry DSN + Metrics Audit Implementation Plan" is in the
wrong location; move its content into the repository's single-context docs
layout by converting the plan into an ADR-style entry and placing it under the
repository's docs/adr directory, then update the root CONTEXT.md to reference
the new ADR (or remove the stray plan file); ensure the ADR uses the same title
and metadata so reviewers can find it and delete the original plan file from
docs/superpowers/plans to avoid duplication.

Comment thread docs/superpowers/plans/2026-05-19-sentry-dsn-metrics-audit.md Outdated
Comment thread openbot/infrastructure/adapters/github.py
Comment on lines +75 to +83
def _extract_reply(result: dict[str, Any]) -> str:
messages = result.get("messages")
if not isinstance(messages, list) or not messages:
raise ValueError("deepagents_result_missing_messages")
content = getattr(messages[-1], "content", None)
reply = _extract_message_text(content)
if not reply:
raise ValueError("deepagents_result_missing_text")
return reply
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle dict-shaped final messages in reply extraction.

On Line 79, reply extraction assumes an object with .content. If messages[-1] is a dict, valid replies can be dropped and trigger deepagents_result_missing_text.

🔧 Proposed compatibility patch
 def _extract_reply(result: dict[str, Any]) -> str:
     messages = result.get("messages")
     if not isinstance(messages, list) or not messages:
         raise ValueError("deepagents_result_missing_messages")
-    content = getattr(messages[-1], "content", None)
+    last = messages[-1]
+    content = last.get("content") if isinstance(last, dict) else getattr(last, "content", None)
     reply = _extract_message_text(content)
     if not reply:
         raise ValueError("deepagents_result_missing_text")
     return reply
For deepagents 0.6.1, what is the returned structure of create_deep_agent(...).ainvoke(...), specifically the type/shape of result["messages"] items?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openbot/infrastructure/agents/deepagents_chat.py` around lines 75 - 83, The
_extract_reply function assumes messages[-1] is an object with a .content
attribute and drops valid replies when the final message is a dict; update
_extract_reply to handle both dict and object shapes by retrieving content via
messages[-1].get("content") if isinstance(messages[-1], dict) otherwise
getattr(messages[-1], "content", None), then pass that content to
_extract_message_text to avoid spurious deepagents_result_missing_text errors
(refer to _extract_reply and messages[-1]).

Comment on lines +93 to +109
def _attach_sentry_tags(payload: QueuePayload) -> None:
"""Push delivery context to the Sentry scope for the current dispatch.

Tags appear in the Sentry UI as filterable dimensions, allowing ops to
jump from a Sentry issue directly to the originating GitHub delivery_id.
Wrapped in try/except so a missing sentry_sdk dep (slim CI image) never
crashes the consumer loop.
"""
try:
import sentry_sdk

sentry_sdk.set_tag("delivery_id", payload.delivery_id)
sentry_sdk.set_tag("feature", payload.feature)
sentry_sdk.set_tag("repo", payload.repo)
except Exception:
pass # Sentry tagging is best-effort; never crash the dispatch loop

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For sentry-sdk Python 2.x, is sentry_sdk.set_tag(...)on the current scope shared across asyncio tasks unlesspush_scope() is used? Please cite the official scope/isolation docs.

💡 Result:

In the Sentry Python SDK (version 2.0 and later), sentry_sdk.set_tag(...) writes data to the current isolation scope [1][2]. Because the isolation scope is implemented using contextvars (or thread-local storage in older environments), it is designed to be isolated per "concurrency unit," such as an asyncio task [3][1][2]. When using asyncio, the AsyncioIntegration automatically manages the propagation of these scopes [3][4]. If your application is correctly configured with this integration, the SDK ensures that each asyncio task has its own independent isolation scope [3]. Consequently, tags set within one task will not leak into other concurrent tasks [3][1]. If you need to manually isolate data for a specific block of code (e.g., a background task or a specific operation), you should use sentry_sdk.isolation_scope() [3][5]. This creates a new, forked isolation scope that is active only for the duration of the with block [3][1][6]. Once the block exits, the SDK restores the previous isolation scope, ensuring that any tags added within the block do not persist or bleed into the surrounding execution context [1][5][6]. In summary, you do not need to manually manage scope isolation for standard asyncio tasks if the AsyncioIntegration is enabled, as the SDK handles this automatically via contextvars [3][4]. You should use isolation_scope() only when you need to explicitly create a temporary, isolated context for a specific operation [3][1]. References: [3] https://docs.sentry.io/platforms/python/troubleshooting [4] https://docs.sentry.io/platforms/python/integrations/asyncio/ [1] https://develop.sentry.dev/sdk/telemetry/scopes [2] https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/scope.py [5] getsentry/sentry#73423

Citations:


🏁 Script executed:

# Find where sentry_sdk is initialized and configured
find . -type f -name "*.py" -exec grep -l "sentry_sdk\|AsyncioIntegration" {} \; | head -20

Repository: YiAgent/openbot

Length of output: 273


🏁 Script executed:

# Check dependencies to see sentry-sdk version
find . -type f \( -name "requirements*.txt" -o -name "pyproject.toml" -o -name "setup.py" -o -name "setup.cfg" \) | xargs cat 2>/dev/null | grep -i sentry

Repository: YiAgent/openbot

Length of output: 350


🏁 Script executed:

# Check the actual worker.py file to understand the execution model
head -150 openbot/infrastructure/queue/worker.py | tail -100

Repository: YiAgent/openbot

Length of output: 3661


🏁 Script executed:

# Check sentry initialization and integration setup
cat openbot/infrastructure/observability.py

Repository: YiAgent/openbot

Length of output: 6138


🏁 Script executed:

# Also check sentry_metrics.py for any integration details
cat openbot/core/sentry_metrics.py

Repository: YiAgent/openbot

Length of output: 2222


🏁 Script executed:

# Check how consume_loop is invoked and whether multiple concurrent tasks are created
grep -n "consume_loop\|asyncio\|gather\|Task" openbot/infrastructure/queue/worker.py | head -40

Repository: YiAgent/openbot

Length of output: 468


🏁 Script executed:

# Also check the runner.py or main entry point to see how many workers/consumers are spawned
find . -type f -name "*runner*" | grep -E "(queue|infrastructure)" | head -5

Repository: YiAgent/openbot

Length of output: 41


🏁 Script executed:

# Find the runner or main entry point
find . -type f -name "*.py" -path "*/queue/*" | xargs grep -l "runner\|main\|__main__" | head -5

Repository: YiAgent/openbot

Length of output: 272


🏁 Script executed:

# Also check if there's a runner module
find . -type f -name "*runner*" -o -name "*main*" | grep -v __pycache__ | head -10

Repository: YiAgent/openbot

Length of output: 323


🏁 Script executed:

# Look for where consume_loop is called and if multiple are spawned
rg "consume_loop" --type py -B2 -A2

Repository: YiAgent/openbot

Length of output: 7241


🏁 Script executed:

# Check the worker entry point to confirm multiple concurrent consume_loop tasks
cat openbot/entrypoints/worker/__main__.py | grep -A20 "asyncio.create_task\|asyncio.gather"

Repository: YiAgent/openbot

Length of output: 1948


🏁 Script executed:

# Check if isolation_scope is mentioned or used anywhere in the codebase
rg "isolation_scope" --type py

Repository: YiAgent/openbot

Length of output: 41


🏁 Script executed:

# Check if push_scope is mentioned in the codebase
rg "push_scope" --type py

Repository: YiAgent/openbot

Length of output: 172


🏁 Script executed:

# Look at the full dispatch call site to understand the context better
sed -n '340,380p' openbot/infrastructure/queue/worker.py

Repository: YiAgent/openbot

Length of output: 1786


Scope Sentry tags per dispatch to prevent cross-job tag bleed.

The code at line 358 calls _attach_sentry_tags(payload), which uses sentry_sdk.set_tag(...) without a scope, yet the preceding comment (line 356–357) claims tags "don't bleed into concurrent dispatches" via scoping. This is incorrect:

  1. No AsyncioIntegration enabled: The sentry initialization (observability.py) registers only StarletteIntegration, FastApiIntegration, and HttpxIntegration. Without AsyncioIntegration, contextvars-based isolation is not guaranteed.
  2. Concurrent dispatches: The worker spawns N consumers via asyncio.create_task(...) for i in range(settings.worker_concurrency) and runs them with asyncio.gather(), so tags set in one dispatch can leak into another.

Wrap the dispatch in an explicit scope (e.g., sentry_sdk.push_scope() or sentry_sdk.isolation_scope()) to isolate tags per handler.

♻️ Proposed fix
+from contextlib import contextmanager
+
+@contextmanager
+def _sentry_scope(payload: QueuePayload):
+    try:
+        import sentry_sdk
+    except Exception:
+        yield
+        return
+
+    with sentry_sdk.push_scope() as scope:
+        scope.set_tag("delivery_id", payload.delivery_id)
+        scope.set_tag("feature", payload.feature)
+        scope.set_tag("repo", payload.repo)
+        yield
-
-def _attach_sentry_tags(payload: QueuePayload) -> None:
-    ...
-    _attach_sentry_tags(payload)
-
     try:
-        try:
-            await run_dispatch(
-                adapter=adapter,
-                event=event,
-                dispatch=new_dispatch,
-                session_factory=session_factory,
-                redis=redis,
-                check_run_id=payload.check_run_id,
-            )
+        with _sentry_scope(payload):
+            try:
+                await run_dispatch(
+                    adapter=adapter,
+                    event=event,
+                    dispatch=new_dispatch,
+                    session_factory=session_factory,
+                    redis=redis,
+                    check_run_id=payload.check_run_id,
+                )
+            except Exception:
+                ...
-        except Exception:
-            ...

Also applies to: 355–360

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openbot/infrastructure/queue/worker.py` around lines 93 - 109, The Sentry
tags are currently set globally which can leak between concurrent consumers;
update _attach_sentry_tags to create an explicit per-dispatch Sentry scope and
set tags inside it (use sentry_sdk.push_scope() as a context manager, or
sentry_sdk.isolation_scope() if available), keeping the existing try/except so
missing sentry_sdk is still safe, and ensure the dispatch/handler that currently
calls _attach_sentry_tags invokes it within that scope (or let
_attach_sentry_tags itself manage the scope) so tags (delivery_id, feature,
repo) are isolated per job and cannot bleed across asyncio tasks.

Comment on lines +47 to +51
assert "GitHub maintainer bot" in seen["kwargs"]["system_prompt"]
prompt = seen["payload"]["messages"][0]["content"]
assert "YiAgent/openbot" in prompt
assert "issue #42" in prompt
assert "summarize this" in prompt
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move prompt-content assertions out of tests/ and into evals/.

Line 47-Line 51 assert prompt wording/content in unit tests, which violates the test policy and makes the suite brittle to prompt iteration. Keep deterministic contract assertions here and move prompt-quality/content checks to evals.

As per coding guidelines, "Do not put LLM-behavior or prompt-quality assertions in tests/ — those belong in evals/."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/infrastructure/agents/test_deepagents_chat.py` around lines 47 - 51,
The test currently asserts exact prompt wording (checks for "GitHub maintainer
bot" in seen["kwargs"]["system_prompt"] and several substrings in prompt =
seen["payload"]["messages"][0]["content"] like "YiAgent/openbot", "issue `#42`",
"summarize this"); remove those literal prompt-content assertions from
tests/infrastructure/agents/test_deepagents_chat.py and instead assert only
deterministic contract properties (e.g., that seen contains kwargs.system_prompt
and payload.messages is a non-empty list or has expected message count/type)
using the existing variables seen, system_prompt, payload and messages; move the
exact prompt-quality checks into an eval under evals/ that verifies the prompt
wording and intent.

Comment on lines +53 to +54
def test_debug_echo_is_disabled_by_default() -> None:
assert Settings().debug_echo_enabled is False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the default-value assertion environment-isolated.

Line 53 can fail if OPENBOT_DEBUG_ECHO_ENABLED is set in the runner environment. Explicitly clear it in-test to keep this deterministic.

Suggested fix
-def test_debug_echo_is_disabled_by_default() -> None:
+def test_debug_echo_is_disabled_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.delenv("OPENBOT_DEBUG_ECHO_ENABLED", raising=False)
     assert Settings().debug_echo_enabled is False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_debug_echo_is_disabled_by_default() -> None:
assert Settings().debug_echo_enabled is False
def test_debug_echo_is_disabled_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("OPENBOT_DEBUG_ECHO_ENABLED", raising=False)
assert Settings().debug_echo_enabled is False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/infrastructure/test_config_blank_coercion.py` around lines 53 - 54, The
test asserts the default for Settings().debug_echo_enabled but can be influenced
by the environment var OPENBOT_DEBUG_ECHO_ENABLED; modify the test to ensure
environment isolation by clearing or unsetting OPENBOT_DEBUG_ECHO_ENABLED before
instantiating Settings (e.g., using monkeypatch.delenv or os.environ.pop) so
Settings() reads a clean environment, then assert debug_echo_enabled is False;
ensure the env change applies only for the test scope so other tests aren’t
affected.

@yibots
Copy link
Copy Markdown

yibots Bot commented May 19, 2026

🤖 OpenBot received this PR and review is queued.

v0.1 alpha: only the ACK is automated so far. Full review (diff scan, structured findings, severity filter) lands in an upcoming commit.

2 similar comments
@yibots
Copy link
Copy Markdown

yibots Bot commented May 19, 2026

🤖 OpenBot received this PR and review is queued.

v0.1 alpha: only the ACK is automated so far. Full review (diff scan, structured findings, severity filter) lands in an upcoming commit.

@yibots
Copy link
Copy Markdown

yibots Bot commented May 20, 2026

🤖 OpenBot received this PR and review is queued.

v0.1 alpha: only the ACK is automated so far. Full review (diff scan, structured findings, severity filter) lands in an upcoming commit.

YiWang24 added 15 commits May 19, 2026 20:24
- redact live Sentry DSN from plan/spec docs (rotate in Sentry dashboard)
- delete obser.txt scratch artifact
- fix .env.example: OPENBOT_SENTRY_PROFILES_SAMPLE_RATE → OPENBOT_SENTRY_PROFILE_SESSION_SAMPLE_RATE
- fix comments: anthropic_api_base alias description, environment field proximity
- add SSRF validator to Settings.anthropic_api_base (rejects non-https and private IPs)
- extend _blank_to_none to cover anthropic_api_base (blank env var → None)
- add LiteLLMCompleter.complete() GLM proxy injection (mirrors module-level complete())
- add debug logging to sentry_profiler_start/stop_failed except blocks
- tighten test_obs.py: assert LoggingIntegration breadcrumb/event levels (WARNING/ERROR)
- add tests/core/test_settings_anthropic_base.py: 8 tests covering SSRF validator
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
tests/core/test_settings_anthropic_base.py (1)

57-79: ⚡ Quick win

Use precise exception assertions for invalid URL cases.

pytest.raises(Exception) is too broad here; it can pass on unrelated errors. Assert the concrete validation exception used by settings loading.

Proposed tightening
 import pytest
+from pydantic import ValidationError
@@
-    with pytest.raises(Exception, match="https://"):
+    with pytest.raises(ValidationError, match="https://"):
         Settings(_env_file=None)  # type: ignore[call-arg]
@@
-    with pytest.raises(Exception, match="private"):
+    with pytest.raises(ValidationError, match="private"):
         Settings(_env_file=None)  # type: ignore[call-arg]
@@
-    with pytest.raises(Exception, match=r"private|loopback"):
+    with pytest.raises(ValidationError, match=r"private|loopback"):
         Settings(_env_file=None)  # type: ignore[call-arg]
@@
-    with pytest.raises(Exception, match=r"169\.254\.169\.254|metadata"):
+    with pytest.raises(ValidationError, match=r"169\.254\.169\.254|metadata"):
         Settings(_env_file=None)  # type: ignore[call-arg]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/core/test_settings_anthropic_base.py` around lines 57 - 79, Replace the
broad pytest.raises(Exception) assertions with the concrete validation exception
raised by the Settings loader (e.g., ValidationError or SettingsError used by
your config code) so tests only pass for the intended validation failures;
update the asserts in test_anthropic_api_base_rejects_private_ip,
test_anthropic_api_base_rejects_loopback,
test_anthropic_api_base_rejects_metadata_endpoint (and the earlier URL test) to
use that specific exception type when constructing Settings(_env_file=None)
after monkeypatching CLAUDE_SWITCH_GLM_BASE_URL and keep the same match patterns
for the error message.
tests/infrastructure/test_obs.py (1)

74-79: ⚡ Quick win

Mock LoggingIntegration directly to assert constructor arguments instead of private SDK internals.

Lines 74-79 test unstable private attributes (_breadcrumb_handler, _handler), which can change across Sentry SDK versions. Instead, mock LoggingIntegration and assert it was called with the correct args: level=logging.WARNING and event_level=logging.ERROR (as you pass in the production code at openbot/infrastructure/observability.py).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/infrastructure/test_obs.py` around lines 74 - 79, Replace the fragile
assertions against private SDK internals by mocking the Sentry SDK
LoggingIntegration constructor in the test and asserting it was called with
level=logging.WARNING and event_level=logging.ERROR (matching how
LoggingIntegration is constructed in observability.py); remove the checks of
logging_int._breadcrumb_handler and logging_int._handler and instead verify the
mock's call args for LoggingIntegration to ensure correct constructor parameters
were passed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-05-19-sentry-continuous-profiling.md`:
- Around line 1-25: The plan file for Sentry continuous profiling is placed in
the per-domain plans folder instead of the repo's single-context docs layout;
move the content of this plan (the Sentry Continuous Profiling Implementation
Plan / 2026-05-19 spec) out of the domain-specific docs and into the repository
single-context structure by adding or updating the root CONTEXT.md to reference
it and placing the plan under the centralized ADR/docs area (docs/adr or
equivalent), ensuring the spec link in the plan still points to
docs/superpowers/specs/2026-05-19-sentry-continuous-profiling-design.md and that
any references to Settings, init_sentry(), start_profiler(), and stop_profiler()
remain intact.

In `@docs/superpowers/plans/2026-05-19-sentry-dsn-metrics-audit.md`:
- Around line 35-37: The fenced code blocks that currently contain the
environment snippet (e.g. the block with "# OPENBOT_SENTRY_DSN=") are unlabeled
and trigger MD040; update each opening triple-backtick to include an appropriate
language identifier (for example use ```bash or ```text for environment variable
snippets) for the block containing "# OPENBOT_SENTRY_DSN=" and the other similar
unlabeled blocks referenced nearby so the markdownlint rule is satisfied.

In `@docs/superpowers/plans/2026-05-19-sentry-logs.md`:
- Around line 185-192: In the docs section containing the dependency snippet
(the fenced block with the line "sentry-sdk[fastapi]>=2.35.0"), the code fences
are unlabeled and triggering MD040; edit the fenced code blocks in this section
to add a language tag (e.g., "text" or "bash") immediately after the opening
backticks so the block that currently shows "sentry-sdk[fastapi]>=2.35.0"
becomes a labeled fence (for example ```text or ```bash), which will satisfy the
linter.

In `@docs/superpowers/specs/2026-05-19-sentry-continuous-profiling-design.md`:
- Around line 130-145: The fenced data-flow block is missing a language tag and
triggers MD040; update the opening fence from ``` to ```text for the block that
contains the process flow (the block showing init_sentry(), start_profiler(),
stop_profiler() and SDK flush behavior) so the markdownlint rule is satisfied
and the snippet is treated as plain text.

In `@docs/superpowers/specs/2026-05-19-sentry-dsn-metrics-audit-design.md`:
- Around line 30-35: The fenced code blocks containing the metric function
signatures (count, distribution, gauge, time) are unlabeled and trigger MD040;
update each block to declare a language (e.g., use "text") so the blocks become
"```text" instead of "```"; apply the same change to the other unlabeled blocks
referenced (around the blocks showing similar function signatures at the later
occurrences) so all fenced blocks include a language label.

In `@docs/superpowers/specs/2026-05-19-sentry-logs-design.md`:
- Around line 31-33: The markdown fenced code blocks containing dependency lines
like "sentry-sdk[fastapi]>=2.35.0" (and the other fenced block around the later
section) are unlabeled and trigger MD040; add an explicit language tag to each
fence (e.g., ```text or ```bash or ```pip depending on intent) so the fences
become annotated (for example, change ``` to ```text) and ensure both
occurrences (the shown dependency snippet and the block in the 89-100 region)
are updated.

In `@openbot/core/settings.py`:
- Around line 204-221: The validator currently only rejects private IPs but
allows empty or "localhost" hostnames; update the check in the
anthropic_api_base validation (the block that computes hostname from parsed and
checks addr/is_private/etc.) to reject empty hostnames and literal "localhost"
(case-insensitive) before doing IP parsing — e.g., if hostname == "" or
hostname.lower() == "localhost": raise ValueError(...) with the same style
message — and also add "localhost" to the _blocked set that currently contains
"169.254.169.254" and "metadata.google.internal" so cloud metadata and loopback
hostnames are both explicitly blocked.

---

Nitpick comments:
In `@tests/core/test_settings_anthropic_base.py`:
- Around line 57-79: Replace the broad pytest.raises(Exception) assertions with
the concrete validation exception raised by the Settings loader (e.g.,
ValidationError or SettingsError used by your config code) so tests only pass
for the intended validation failures; update the asserts in
test_anthropic_api_base_rejects_private_ip,
test_anthropic_api_base_rejects_loopback,
test_anthropic_api_base_rejects_metadata_endpoint (and the earlier URL test) to
use that specific exception type when constructing Settings(_env_file=None)
after monkeypatching CLAUDE_SWITCH_GLM_BASE_URL and keep the same match patterns
for the error message.

In `@tests/infrastructure/test_obs.py`:
- Around line 74-79: Replace the fragile assertions against private SDK
internals by mocking the Sentry SDK LoggingIntegration constructor in the test
and asserting it was called with level=logging.WARNING and
event_level=logging.ERROR (matching how LoggingIntegration is constructed in
observability.py); remove the checks of logging_int._breadcrumb_handler and
logging_int._handler and instead verify the mock's call args for
LoggingIntegration to ensure correct constructor parameters were passed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 40fedb50-c62c-4767-aaca-11bb081f3b51

📥 Commits

Reviewing files that changed from the base of the PR and between 76f0766 and 4045521.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • .env.example
  • docs/superpowers/plans/2026-05-19-sentry-continuous-profiling.md
  • docs/superpowers/plans/2026-05-19-sentry-dsn-metrics-audit.md
  • docs/superpowers/plans/2026-05-19-sentry-logs.md
  • docs/superpowers/specs/2026-05-19-sentry-continuous-profiling-design.md
  • docs/superpowers/specs/2026-05-19-sentry-dsn-metrics-audit-design.md
  • docs/superpowers/specs/2026-05-19-sentry-logs-design.md
  • openbot/core/settings.py
  • openbot/entrypoints/api/app.py
  • openbot/entrypoints/worker/__main__.py
  • openbot/infrastructure/llm/complete.py
  • openbot/infrastructure/observability.py
  • pyproject.toml
  • tests/core/test_settings_anthropic_base.py
  • tests/infrastructure/test_obs.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • .env.example
  • openbot/entrypoints/worker/main.py
  • openbot/infrastructure/llm/complete.py
  • openbot/entrypoints/api/app.py

Comment on lines +1 to +25
# Sentry Continuous Profiling Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Replace transaction-based profiling (`profiles_sample_rate`) with continuous profiling (`profile_session_sample_rate` + `start_profiler()`) in both the webapp and worker entrypoints.

**Architecture:** `sentry_sdk.init()` receives `profile_session_sample_rate` (configured via Settings); the entrypoints own the profiler lifecycle — `start_profiler()` at startup, `stop_profiler()` in the webapp's shutdown block (worker relies on process-exit flush). Both profiler calls are wrapped in `try/except Exception: pass` for graceful degradation.

**Tech Stack:** Python 3.12, sentry-sdk ≥ 2.35.0 (already installed at 2.60.0), pytest, pydantic-settings

**Spec:** `docs/superpowers/specs/2026-05-19-sentry-continuous-profiling-design.md`

---

## File map

| Action | Path | Purpose |
|---|---|---|
| Modify | `openbot/core/settings.py` | Rename field + default 1.0 |
| Modify | `openbot/infrastructure/observability.py` | Swap `profiles_sample_rate` → `profile_session_sample_rate` |
| Modify | `openbot/entrypoints/api/app.py` | `start_profiler()` on startup, `stop_profiler()` in finally |
| Modify | `openbot/entrypoints/worker/__main__.py` | `start_profiler()` after `init_sentry()` |
| Modify | `tests/infrastructure/test_obs.py` | Swap assertion + Settings constructor |

---
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move this plan/spec documentation set into the repository’s single-context docs layout.

This file is added under docs/superpowers/plans/, which conflicts with the documented domain-doc structure for this repo.

As per coding guidelines, "Use a single-context layout for domain docs: root CONTEXT.md + docs/adr/ (lazily created — silent if absent)".

🧰 Tools
🪛 LanguageTool

[grammar] ~5-~5: Ensure spelling is correct
Context: ...rate+start_profiler()`) in both the webapp and worker entrypoints. **Architecture...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-19-sentry-continuous-profiling.md` around
lines 1 - 25, The plan file for Sentry continuous profiling is placed in the
per-domain plans folder instead of the repo's single-context docs layout; move
the content of this plan (the Sentry Continuous Profiling Implementation Plan /
2026-05-19 spec) out of the domain-specific docs and into the repository
single-context structure by adding or updating the root CONTEXT.md to reference
it and placing the plan under the centralized ADR/docs area (docs/adr or
equivalent), ensuring the spec link in the plan still points to
docs/superpowers/specs/2026-05-19-sentry-continuous-profiling-design.md and that
any references to Settings, init_sentry(), start_profiler(), and stop_profiler()
remain intact.

Comment on lines +35 to +37
```
# OPENBOT_SENTRY_DSN=
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks.

These fences are unlabeled and trigger markdownlint MD040; please label them (text, bash, etc.) to keep docs lint-clean.

Also applies to: 39-41, 54-56

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-19-sentry-dsn-metrics-audit.md` around lines
35 - 37, The fenced code blocks that currently contain the environment snippet
(e.g. the block with "# OPENBOT_SENTRY_DSN=") are unlabeled and trigger MD040;
update each opening triple-backtick to include an appropriate language
identifier (for example use ```bash or ```text for environment variable
snippets) for the block containing "# OPENBOT_SENTRY_DSN=" and the other similar
unlabeled blocks referenced nearby so the markdownlint rule is satisfied.

Comment on lines +185 to +192
```
"sentry-sdk[fastapi]>=2.18",
```

Replace with:
```
"sentry-sdk[fastapi]>=2.35.0",
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Label the fenced code blocks with a language.

The blocks in this section are unlabeled and flagged by MD040; adding text (or a specific language) will resolve the lint warning.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 185-185: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 190-190: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-19-sentry-logs.md` around lines 185 - 192, In
the docs section containing the dependency snippet (the fenced block with the
line "sentry-sdk[fastapi]>=2.35.0"), the code fences are unlabeled and
triggering MD040; edit the fenced code blocks in this section to add a language
tag (e.g., "text" or "bash") immediately after the opening backticks so the
block that currently shows "sentry-sdk[fastapi]>=2.35.0" becomes a labeled fence
(for example ```text or ```bash), which will satisfy the linter.

Comment on lines +130 to +145
```
Process starts
└─ init_sentry() # configures SDK with profile_session_sample_rate=1.0
└─ start_profiler() # begins continuous profiling session

Process runs
└─ profiler samples stack traces every ~10ms → buffered in SDK
└─ SDK flushes profile data to Sentry ingest on interval

webapp: SIGTERM → lifespan finally
└─ stop_profiler() # flush remaining profile data
└─ adapter/redis/db teardown

worker: SIGTERM → shutdown event → asyncio.run() returns
└─ SDK atexit hook flushes remaining profile data (no explicit stop needed)
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify a language for the data-flow fenced block.

This block is currently unlabeled and triggers MD040. Use text to satisfy markdownlint.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-19-sentry-continuous-profiling-design.md`
around lines 130 - 145, The fenced data-flow block is missing a language tag and
triggers MD040; update the opening fence from ``` to ```text for the block that
contains the process flow (the block showing init_sentry(), start_profiler(),
stop_profiler() and SDK flush behavior) so the markdownlint rule is satisfied
and the snippet is treated as plain text.

Comment on lines +30 to +35
```
count(name, value, unit=None, attributes=None)
distribution(name, value, unit=None, attributes=None)
gauge(name, value, unit=None, attributes=None)
time(name, value, unit=None, attributes=None)
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix unlabeled fenced code blocks (MD040).

These fenced blocks should declare a language (text is fine where not executable) to pass markdown lint checks.

Also applies to: 60-62, 99-105

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-19-sentry-dsn-metrics-audit-design.md` around
lines 30 - 35, The fenced code blocks containing the metric function signatures
(count, distribution, gauge, time) are unlabeled and trigger MD040; update each
block to declare a language (e.g., use "text") so the blocks become "```text"
instead of "```"; apply the same change to the other unlabeled blocks referenced
(around the blocks showing similar function signatures at the later occurrences)
so all fenced blocks include a language label.

Comment on lines +31 to +33
```
"sentry-sdk[fastapi]>=2.35.0", # was >=2.18 — Logs feature requires 2.35+
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language tags to fenced blocks.

The unlabeled fences here are flagged by MD040; please annotate them (text, python, etc.) to keep lint green.

Also applies to: 89-100

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 31-31: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-19-sentry-logs-design.md` around lines 31 -
33, The markdown fenced code blocks containing dependency lines like
"sentry-sdk[fastapi]>=2.35.0" (and the other fenced block around the later
section) are unlabeled and trigger MD040; add an explicit language tag to each
fence (e.g., ```text or ```bash or ```pip depending on intent) so the fences
become annotated (for example, change ``` to ```text) and ensure both
occurrences (the shown dependency snippet and the block in the 89-100 region)
are updated.

Comment thread openbot/core/settings.py
Comment on lines +204 to +221
hostname = parsed.hostname or ""
try:
addr = ipaddress.ip_address(hostname)
if addr.is_private or addr.is_loopback or addr.is_link_local:
raise ValueError(
f"anthropic_api_base must not target a private/loopback address: {hostname}"
)
except ValueError as exc:
# Re-raise our own errors; ignore "not a valid IP" (i.e. hostname).
if "anthropic_api_base" in str(exc):
raise
# Block AWS/GCP/Azure instance metadata hostnames explicitly.
_blocked = {"169.254.169.254", "metadata.google.internal"}
if hostname in _blocked:
raise ValueError(
f"anthropic_api_base must not target a cloud metadata endpoint: {hostname}"
)
return v
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block localhost/empty-host targets in anthropic_api_base validation.

https://localhost/... currently passes because only literal IPs are screened. That leaves a loopback SSRF path open.

🔒 Proposed fix
-        hostname = parsed.hostname or ""
+        hostname = (parsed.hostname or "").lower()
+        if not hostname:
+            raise ValueError("anthropic_api_base must include a hostname")
+        if hostname == "localhost" or hostname.endswith(".localhost"):
+            raise ValueError(
+                "anthropic_api_base must not target localhost/loopback hostnames"
+            )
         try:
             addr = ipaddress.ip_address(hostname)
             if addr.is_private or addr.is_loopback or addr.is_link_local:
                 raise ValueError(
                     f"anthropic_api_base must not target a private/loopback address: {hostname}"
                 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openbot/core/settings.py` around lines 204 - 221, The validator currently
only rejects private IPs but allows empty or "localhost" hostnames; update the
check in the anthropic_api_base validation (the block that computes hostname
from parsed and checks addr/is_private/etc.) to reject empty hostnames and
literal "localhost" (case-insensitive) before doing IP parsing — e.g., if
hostname == "" or hostname.lower() == "localhost": raise ValueError(...) with
the same style message — and also add "localhost" to the _blocked set that
currently contains "169.254.169.254" and "metadata.google.internal" so cloud
metadata and loopback hostnames are both explicitly blocked.

@YiWang24 YiWang24 force-pushed the feat/observability-and-infra-updates branch from 4045521 to b40e384 Compare May 20, 2026 00:25
@yibots
Copy link
Copy Markdown

yibots Bot commented May 20, 2026

🤖 OpenBot received this PR and review is queued.

v0.1 alpha: only the ACK is automated so far. Full review (diff scan, structured findings, severity filter) lands in an upcoming commit.

@YiWang24 YiWang24 changed the title feat: Sentry DSN wiring + metrics shim audit (dead set() removed, 11 new tests) feat: sentry DSN wiring + metrics shim audit (dead set() removed, 11 new tests) May 20, 2026
@YiWang24 YiWang24 merged commit 259d732 into main May 20, 2026
7 of 8 checks passed
@YiWang24 YiWang24 deleted the feat/observability-and-infra-updates branch May 20, 2026 00:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant