Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request delivers significant improvements across logging, LLM provider management, and the initial setup wizard. It resolves a critical logging issue by ensuring proper Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI (base), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (29)
WalkthroughAdded provider model auto-discovery end-to-end. Backend: new POST /api/v1/providers/{name}/discover-models endpoint, DiscoverModelsResponse DTO, ProviderManagementService.discover_models_for_provider, automatic discovery during preset creation for no-auth presets, and new src/synthorg/providers/discovery.py implementing Ollama (/api/tags) and generic (/models) probes with SSRF-safe URL validation and discovery/failure events. Setup controller DTOs moved to setup_models.py; setup status now includes has_company and has_agents for step resume. Observability: new discovery events and structlog cache_logger_on_first_use toggled. Frontend: discoverModels API, store action, UI navigation/back buttons, provider discovery UI changes, and corresponding tests. 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📝 Coding Plan
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces several valuable fixes and features, including resolving a logging issue, adding provider model auto-discovery with SSRF protection, and significantly improving the setup wizard's user experience. The code is generally well-structured, with new functionality encapsulated in dedicated modules and good test coverage. However, I've identified a critical syntax error in the new helper functions within src/synthorg/api/controllers/setup.py. The exception handling uses Python 2 syntax, which will cause a SyntaxError in Python 3. Addressing this is essential for the code to function correctly.
| try: | ||
| entry = await settings_svc.get_entry("company", "company_name") | ||
| return bool(entry.value and entry.value.strip()) | ||
| except MemoryError, RecursionError: |
| return False | ||
| parsed = json.loads(entry.value) | ||
| return isinstance(parsed, list) and len(parsed) > 0 | ||
| except MemoryError, RecursionError: |
| raw_pw_value = pw_entry.value | ||
| parsed = int(raw_pw_value) | ||
| return max(parsed, _DEFAULT_MIN_PASSWORD_LENGTH) | ||
| except MemoryError, RecursionError: |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/providers/management/service.py`:
- Around line 377-393: After discovering models using get_provider, re-acquire
the provider state under the same protection used for updates (the provider
lock) before calling update_provider: fetch the provider again (call
self.get_provider(name) or the internal locked getter), compare its base_url to
the one you probed (the previous config.base_url used with _infer_preset_hint
and discover_models), and if they differ abort (or optionally retry discovery)
instead of creating UpdateProviderRequest(models=discovered) and calling
self.update_provider(name, ...); ensure this check happens under the lock so
concurrent changes cannot race and you only persist models for a matching
base_url.
- Around line 387-389: The preset inference currently uses
_infer_preset_hint(config.base_url) and passes that to discover_models, but
_infer_preset_hint only returns "ollama" when the parsed port matches the
default Ollama port, causing Ollama instances on custom ports or behind proxies
to fall back to the wrong probe; update the logic around _infer_preset_hint and
discover_models to (a) persist or accept an explicit preset kind when present
instead of deriving solely from urlparse(config.base_url).port, and (b) add an
Ollama fallback probe path in discover_models when the host or path pattern
strongly indicates Ollama (e.g., hostname contains "ollama" or base_url path
includes Ollama-style endpoints) so custom-port or proxied Ollama deployments
are probed via /api/tags; ensure changes reference the variables and functions
_infer_preset_hint, preset_hint, discover_models, and config.base_url.
- Around line 389-393: Discovery currently calls
discover_models(config.base_url, preset_hint) without provider auth or client
policy; update the call site to pass the full ProviderConfig (including api_key,
oauth, headers and client policy) so discover_models can use
BaseCompletionProvider path with retry/rate-limiting, i.e., replace the two-arg
call with one that forwards the ProviderConfig (or add an overload that accepts
ProviderConfig) and ensure discover_models uses BaseCompletionProvider to
perform requests; alternatively, if threading ProviderConfig into discovery is
not yet implemented, explicitly reject providers whose config.auth_type !=
AuthType.NONE in the discover flow (before calling discover_models) and return
an error, referencing discover_models, update_provider, UpdateProviderRequest,
ProviderConfig and BaseCompletionProvider so reviewers can locate the change.
In `@web/src/components/setup/SetupProvider.vue`:
- Around line 181-188: The current mount logic auto-sets testPassed = true when
hasModels.value is true, meaning existing providers are treated as verified
without running a connection check; change this to trigger the same validation
routine used by the manual "Test connection" flow instead of flipping testPassed
directly: locate the component's test handler (e.g., runProviderTest /
testProviderConnection) and call it after setting createdProviderName.value (or
implement a new function that runs the connection check and sets testPassed
based on the result), or add a configurable flag (e.g., skipAutoTest) to
preserve the old behavior when desired.
🪄 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: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: af0c40de-86fc-4e39-aef7-8358e190b4af
📒 Files selected for processing (28)
CLAUDE.mddocs/design/operations.mdsrc/synthorg/api/controllers/providers.pysrc/synthorg/api/controllers/setup.pysrc/synthorg/api/controllers/setup_models.pysrc/synthorg/api/dto.pysrc/synthorg/observability/events/provider.pysrc/synthorg/observability/setup.pysrc/synthorg/providers/discovery.pysrc/synthorg/providers/management/service.pytests/conftest.pytests/unit/api/controllers/test_providers.pytests/unit/api/controllers/test_setup.pytests/unit/observability/test_setup.pytests/unit/providers/management/test_service.pytests/unit/providers/test_discovery.pyweb/src/__tests__/router/guards.test.tsweb/src/__tests__/stores/providers.test.tsweb/src/__tests__/stores/setup.test.tsweb/src/__tests__/views/SetupPage.test.tsweb/src/api/endpoints/providers.tsweb/src/api/types.tsweb/src/components/setup/SetupAdmin.vueweb/src/components/setup/SetupAgent.vueweb/src/components/setup/SetupCompany.vueweb/src/components/setup/SetupProvider.vueweb/src/stores/providers.tsweb/src/views/SetupPage.vue
💤 Files with no reviewable changes (1)
- tests/conftest.py
| if (hasProviders.value) { | ||
| testPassed.value = true | ||
| const names = Object.keys(store.providers) | ||
| createdProviderName.value = names[0] ?? null | ||
| // Auto-pass test if provider already has models. | ||
| if (hasModels.value) { | ||
| testPassed.value = true | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Clarify the auto-pass behavior for existing providers with models.
When an existing provider has models on mount, testPassed is set to true without actually running a connection test. This is likely intentional to allow progression when returning to a previously-configured provider, but it means the connection hasn't been verified in the current session.
Consider whether this is the desired UX, or if a test should be required each time the user reaches this step (which would be stricter but more annoying for users resuming setup).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/setup/SetupProvider.vue` around lines 181 - 188, The
current mount logic auto-sets testPassed = true when hasModels.value is true,
meaning existing providers are treated as verified without running a connection
check; change this to trigger the same validation routine used by the manual
"Test connection" flow instead of flipping testPassed directly: locate the
component's test handler (e.g., runProviderTest / testProviderConnection) and
call it after setting createdProviderName.value (or implement a new function
that runs the connection check and sets testPassed based on the result), or add
a configurable flag (e.g., skipAutoTest) to preserve the old behavior when
desired.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #634 +/- ##
==========================================
- Coverage 92.47% 92.45% -0.02%
==========================================
Files 554 556 +2
Lines 27872 28045 +173
Branches 2677 2696 +19
==========================================
+ Hits 25775 25930 +155
- Misses 1654 1665 +11
- Partials 443 450 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
10e89c3 to
b40a02d
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/api/controllers/setup_models.py`:
- Around line 113-129: The validator _validate_preset_exists currently mutates a
frozen model via object.__setattr__; change it to run before construction by
replacing `@model_validator`(mode="after") with `@model_validator`(mode="before")
(or add a new before-mode validator) and move the import of PERSONALITY_PRESETS
into that method or top-level as needed; validate the incoming values by
normalizing the input (key = values.get("personality_preset",
"").strip().lower()), raise ValueError if key not in PERSONALITY_PRESETS, then
return a new values dict with "personality_preset" set to the canonical key so
the model is constructed with the normalized value (do not use
object.__setattr__).
In `@src/synthorg/providers/discovery.py`:
- Around line 128-131: socket.getaddrinfo is being called synchronously in
_check_blocked_address (and transitively in _validate_discovery_url), which
blocks the event loop; change _check_blocked_address and _validate_discovery_url
to async and run the blocking call via the event loop executor (e.g.,
loop.run_in_executor or asyncio.to_thread) when calling
socket.getaddrinfo(hostname, None), await the result, and propagate exceptions
as before so DNS lookups do not block the async context.
- Around line 393-400: The log call in _do_fetch_json drops the preset context
by hardcoding preset=None; modify _do_fetch_json to accept a preset_name
parameter (defaulting to None for compatibility) and use that value in the
logger.warning call for PROVIDER_DISCOVERY_FAILED, then update callers (notably
_fetch_json) to pass their preset_name through when invoking _do_fetch_json so
the warning includes the correct preset context.
In `@src/synthorg/providers/management/service.py`:
- Around line 625-647: _build_discovery_headers currently ignores AuthType.OAUTH
and returns None, causing discovery requests to run unauthenticated; update
_build_discovery_headers to explicitly handle AuthType.OAUTH by either
attempting to obtain an OAuth token from ProviderConfig (e.g., a token getter on
ProviderConfig or an oauth_client) and returning {"Authorization": f"Bearer
{token}"} if available, or — if token acquisition is out of scope — emit a clear
log message indicating OAuth-based discovery is being skipped (use the module
logger) and then return None; reference ProviderConfig and AuthType.OAUTH in
your change so the OAuth path is explicit.
In `@tests/unit/api/controllers/test_setup.py`:
- Around line 178-212: The tests
test_min_password_length_non_integer_returns_default and
test_min_password_length_below_default_returns_default mutate
settings_repo._store[("api","min_password_length")] but always pop() it in
finally, making test order-dependent; change each test to snapshot the original
value before setting (e.g., orig =
settings_repo._store.get(("api","min_password_length"))) then in finally restore
it by setting it back if orig is not None or pop only if orig was None, ensuring
the original value is preserved when the test exits.
In `@web/src/__tests__/views/SetupPage.test.ts`:
- Around line 181-184: Replace the fragile class-based selector in the test
(currently using '.rounded-xl.bg-brand-600') with a stable test id: change the
assertion in SetupPage.test.ts to find '[data-testid="brand-logo"]' and assert
existence and text as before; then add data-testid="brand-logo" to the logo
container element in the SetupPage component (SetupPage.vue) so the test targets
that attribute instead of presentation classes.
In `@web/src/components/setup/SetupAdmin.vue`:
- Around line 109-126: The Back button can be clicked while a submit is
in-flight, causing a race; update the Back Button (the one with
`@click`="emit('previous')") to also be disabled when auth.loading is true (and/or
when locked is true) by adding the same disabled condition used on the submit
Button (e.g., :disabled="!username || !password || !confirmPassword || locked ||
auth.loading") so navigation is prevented during the async create-admin
operation.
In `@web/src/components/setup/SetupAgent.vue`:
- Around line 244-261: The Back button can be used while agent creation is in
progress, allowing a late completion to still emit a completion event and
advance the wizard; update the Back handler in SetupAgent.vue so it respects the
creating flag—either disable the Back Button when creating is true (bind
:disabled="creating || ...") or change the `@click` handler that calls
emit('previous') to first check/guard if(!creating) emit('previous'); reference
the Back Button element and the creating reactive/property to ensure users
cannot navigate back during an in-flight create request.
In `@web/src/components/setup/SetupCompany.vue`:
- Around line 136-144: The Back button must be disabled while company creation
is in progress to prevent step changes mid-request; bind the Button's disabled
prop to the same loading/ref used by the submit flow (e.g.,
:disabled="isSubmitting" or :disabled="creatingCompany") and ensure that the
submit method sets that reactive boolean true before the async create call and
false after (or on error). If no such flag exists, add a boolean ref
(isSubmitting) in SetupCompany.vue, toggle it in the create/submit handler, and
use :disabled="isSubmitting" on the Button that currently has
`@click`="emit('previous')" so the click is inert while submitting.
🪄 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: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1314e3b2-55b2-4fec-9335-7b7d86f79724
📒 Files selected for processing (28)
CLAUDE.mddocs/design/operations.mdsrc/synthorg/api/controllers/providers.pysrc/synthorg/api/controllers/setup.pysrc/synthorg/api/controllers/setup_models.pysrc/synthorg/api/dto.pysrc/synthorg/observability/events/provider.pysrc/synthorg/observability/setup.pysrc/synthorg/providers/discovery.pysrc/synthorg/providers/management/service.pytests/conftest.pytests/unit/api/controllers/test_providers.pytests/unit/api/controllers/test_setup.pytests/unit/observability/test_setup.pytests/unit/providers/management/test_service.pytests/unit/providers/test_discovery.pyweb/src/__tests__/router/guards.test.tsweb/src/__tests__/stores/providers.test.tsweb/src/__tests__/stores/setup.test.tsweb/src/__tests__/views/SetupPage.test.tsweb/src/api/endpoints/providers.tsweb/src/api/types.tsweb/src/components/setup/SetupAdmin.vueweb/src/components/setup/SetupAgent.vueweb/src/components/setup/SetupCompany.vueweb/src/components/setup/SetupProvider.vueweb/src/stores/providers.tsweb/src/views/SetupPage.vue
💤 Files with no reviewable changes (1)
- tests/conftest.py
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
src/synthorg/providers/management/service.py (2)
414-419:⚠️ Potential issue | 🟠 MajorPort-only preset inference breaks non-default Ollama deployments.
discover_models()only switches to/api/tagswhen the hint is exactly"ollama", but the fallback here derives that hint solely fromurlparse(base_url).port. Any deployment on a custom port, default HTTPS port, or behind a reverse proxy falls through to/models, so manual discovery fails for a valid config. Persist the preset kind or require an explicit hint from the caller.Also applies to: 658-675
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/providers/management/service.py` around lines 414 - 419, The preset hint derivation using _infer_preset_hint(config.base_url) can return only a port so discover_models(config.base_url, resolved_hint, ...) misroutes non-default Ollama deployments; update the logic so the actual preset kind (e.g., "ollama") is preserved or require the caller to pass preset_hint explicitly instead of inferring from port: change _infer_preset_hint to return a full preset identifier (not just port), or add/validate a preset_kind/preset_hint parameter before calling discover_models, and ensure both call sites (where resolved_hint is computed around resolved_hint = preset_hint or _infer_preset_hint(config.base_url) and the repeated block at the other location) pass the canonical preset kind so discover_models can switch to /api/tags correctly.
422-443:⚠️ Potential issue | 🔴 CriticalThe
base_urlre-check still races with the locked update path.The compare happens before
update_provider()acquiresself._lock, so another request can change the provider after the check but before the write. That still allows models discovered from the old endpoint to be persisted onto the new config. Do the re-read, compare, and persist under the same lock or via an internal locked helper. Based on learnings,Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/providers/management/service.py` around lines 422 - 443, The base_url re-check and subsequent update are not atomic because get_provider(name) is called outside the service lock; lock self._lock around re-reading the provider, comparing base_url to config.base_url, and calling update_provider so the check+write are serialized, or add an internal locked helper (e.g. _update_provider_locked) that performs current = await self.get_provider(name), compares current.base_url to config.base_url, and performs await self.update_provider(name, update_req) under the same asyncio.Lock to avoid the TOCTOU race.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/providers/discovery.py`:
- Around line 197-202: The logs currently pass the raw variable url into
logger.warning (e.g., the PROVIDER_DISCOVERY_FAILED calls) which can leak
credentials or tokens; before logging, redact sensitive parts (userinfo and
query) and pass the redacted string (e.g., redacted_url) instead of url. Add or
use a small helper (redact_url) that parses the URL (urllib.parse.urlparse /
urlunparse), strips user:pass and query params (or replaces them with
placeholders), and update all logger.warning / logger.error sites that pass url
(including the occurrences around the PROVIDER_DISCOVERY_FAILED calls and the
other ranges noted) to log the redacted_url variable. Ensure the helper is
reusable and used wherever url is logged.
- Around line 77-98: The SSRF check currently only validates the hostname in
_check_blocked_address but does not return the concrete resolved address,
allowing DNS rebinding between validation and the actual httpx request; modify
_check_blocked_address to return the pinned IP (after calling
_check_ip_blocked/_check_resolved_hostname) instead of/or along with None, and
update _do_fetch_json to use that resolved IP when making the request by either
constructing a numeric-IP URL (preserving original Host header) or creating a
custom httpx transport that binds the connection to the resolved address;
reference _check_blocked_address, _check_ip_blocked, _check_resolved_hostname
for resolution/validation and update _do_fetch_json to accept and use the pinned
address for the actual connection.
---
Duplicate comments:
In `@src/synthorg/providers/management/service.py`:
- Around line 414-419: The preset hint derivation using
_infer_preset_hint(config.base_url) can return only a port so
discover_models(config.base_url, resolved_hint, ...) misroutes non-default
Ollama deployments; update the logic so the actual preset kind (e.g., "ollama")
is preserved or require the caller to pass preset_hint explicitly instead of
inferring from port: change _infer_preset_hint to return a full preset
identifier (not just port), or add/validate a preset_kind/preset_hint parameter
before calling discover_models, and ensure both call sites (where resolved_hint
is computed around resolved_hint = preset_hint or
_infer_preset_hint(config.base_url) and the repeated block at the other
location) pass the canonical preset kind so discover_models can switch to
/api/tags correctly.
- Around line 422-443: The base_url re-check and subsequent update are not
atomic because get_provider(name) is called outside the service lock; lock
self._lock around re-reading the provider, comparing base_url to
config.base_url, and calling update_provider so the check+write are serialized,
or add an internal locked helper (e.g. _update_provider_locked) that performs
current = await self.get_provider(name), compares current.base_url to
config.base_url, and performs await self.update_provider(name, update_req) under
the same asyncio.Lock to avoid the TOCTOU race.
🪄 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: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5dd4dee1-8f2b-4e49-a925-eeb07f857502
📒 Files selected for processing (10)
src/synthorg/api/controllers/setup_models.pysrc/synthorg/providers/discovery.pysrc/synthorg/providers/management/service.pytests/unit/api/controllers/test_setup.pytests/unit/providers/test_discovery.pyweb/src/__tests__/views/SetupPage.test.tsweb/src/components/setup/SetupAdmin.vueweb/src/components/setup/SetupAgent.vueweb/src/components/setup/SetupCompany.vueweb/src/components/setup/SetupWelcome.vue
| if parsed.scheme not in _ALLOWED_SCHEMES: | ||
| return f"scheme {parsed.scheme!r} not allowed; use http or https" | ||
|
|
||
| hostname = parsed.hostname | ||
| if not hostname: | ||
| return "URL has no hostname" | ||
|
|
||
| return await _check_blocked_address(hostname) |
There was a problem hiding this comment.
Blanket loopback/private blocking breaks the supported local discovery flow.
Both the public discover_models() examples and the service call sites feed self-hosted base_urls through this validator, but this branch rejects those URLs unconditionally. As written, auto-discovery and the UI retry path short-circuit to blocked_url for the common local setup, and the HTTP-path tests only exercise those URLs by monkeypatching _validate_discovery_url() away.
… improve setup wizard UX Bug 1: structlog's cache_logger_on_first_use=True caused module-level loggers to retain stale processor chains after configure_logging() was called a second time in _bootstrap_app_logging. Changed to False so loggers always resolve the current chain. Removed the test conftest monkey-patch that was working around the same issue. Bug 2: Ollama/LM Studio/vLLM presets shipped with empty default_models. Added a model auto-discovery service (providers/discovery.py) that queries provider endpoints after preset creation. Ollama uses /api/tags, OpenAI-compatible providers use /models. Added POST discover-models endpoint for manual retry from the UI. Bug 3: Setup wizard now has back buttons on every step (except Welcome), clickable step indicators for completed steps, step resume on page refresh (backend returns has_company/has_agents in status), and guidance when provider has 0 models with a "Retry Discovery" button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes: - Restore timeout-minutes on all 27 CI workflow jobs (reverts accidental removal) - Restore fuzz target discovery error handling in cli.yml - Rename _discover_openai_compatible to _discover_standard_api (vendor name violation) - Extract setup DTOs to setup_models.py (setup.py exceeded 800-line limit) Security fixes: - Add SSRF URL validation to discovery.py (blocked networks, scheme allowlist) - Add follow_redirects=False to httpx client - Add type guard for non-dict JSON responses Code quality: - Split broad except Exception in _check_has_company/_check_has_agents into specific SettingNotFoundError + JSONDecodeError handlers - Add None guard before json.loads in _check_has_agents - Fix _infer_preset_hint to use urlparse instead of substring matching - Reuse _check_has_company helper in complete_setup (removes duplication) - Extract _resolve_min_password_length helper (reduces get_status length) - Add has_company/has_agents to SETUP_STATUS_CHECKED debug log - Add logging for no-base-url in discover_models_for_provider - Add Final annotation to _DISCOVERY_TIMEOUT_SECONDS - Fix duplicate @pytest.mark.unit on TestReconfigurationLogRouting - Add missing @pytest.mark.unit to TestConfigureLoggingIntegration - Remove dead TYPE_CHECKING block from test_discovery.py - Fix test_connection docstring (missing Raises section) - Extract _mock_client helper to reduce test boilerplate Documentation: - CLAUDE.md: add discovery module and discover-models endpoint to package structure - docs/design/operations.md: add model discovery to Provider Management section - docs/security.md: fix "both images" to "all three images" - Restore .grype.yaml audit date to 2026-06-17 Tests (27 new): - TestDiscoverModelsForProvider (4 tests) - TestCreateFromPresetAutoDiscovery (1 test) - TestDiscoverModelsEndpoint (2 tests) - TestValidateDiscoveryUrl (10 parametrized + 1 integration) - TestInferPresetHint (6 parametrized) - Malformed entries tests for Ollama and standard API - Trailing-slash URL normalization test - Non-dict JSON response test - Frontend: discoverModels store action tests (2) - Frontend: prevStep/setStep store tests (8) - Updated all SetupStatusResponse mocks with has_company/has_agents Pre-reviewed by 16 agents, 34 findings addressed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SSRF DNS rebinding protection: resolve hostnames via getaddrinfo and check resolved IPs against blocked networks (not just literal IPs) - TOCTOU fix: re-verify base_url before persisting discovered models - Forward auth headers through model discovery for non-none auth types - Accept preset_hint parameter for endpoint selection in discovery - Fix PROVIDER_NOT_FOUND event misuse (use PROVIDER_DISCOVERY_FAILED) - Add MemoryError/RecursionError re-raise in _fetch_json broad except - Refactor _do_test_connection and _fetch_json under 50-line limit - Strict mode for _check_has_company in complete_setup validation - Log malformed model entries during discovery (silent data loss fix) - Clear stale error in SetupProvider handleTestConnection - SetupCompleteResponse.setup_complete typed as Literal[True] - 15 new tests (SSRF IPv6/edge cases, redirects, JSON edge cases) - Fix docs drift in operations.md API Surface table Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace object.__setattr__ on frozen model with mode="before" validator - Make SSRF validation async (socket.getaddrinfo via asyncio.to_thread) - Pass preset_name through _do_fetch_json for correct log context - Log OAuth discovery skip explicitly in _build_discovery_headers - Snapshot/restore original values in min_password_length tests - Add data-testid="brand-logo" for stable test selectors - Disable Back buttons during in-flight async operations in setup wizard (SetupAdmin, SetupAgent, SetupCompany) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pin resolved IP in discovery requests to prevent DNS rebinding between SSRF validation and the actual HTTP connection - Add _redact_url helper to strip userinfo and query params from URLs before logging (prevents credential leakage in logs) - Atomic _apply_discovered_models: re-check base_url and persist models under a single lock acquisition (eliminates TOCTOU gap) - _validate_discovery_url now returns _SsrfCheckResult with both error and pinned_ip for callers to build IP-pinned requests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ae0c476 to
03fd90a
Compare
🤖 I have created a release *beep* *boop* --- ## [0.3.10](v0.3.9...v0.3.10) (2026-03-20) ### Bug Fixes * **ci:** generate required secrets in DAST workflow ([#623](#623)) ([6ae297f](6ae297f)) * **cli:** doctor image check reads compose file and fix API docs URL ([#625](#625)) ([5202e53](5202e53)) * **engine:** sanitize error messages in checkpoint reconciliation and compaction summaries ([#632](#632)) ([5394ed7](5394ed7)) * mitigate TOCTOU DNS rebinding gap in git clone SSRF prevention ([#633](#633)) ([1846f6e](1846f6e)) * resolve post-startup log loss, add provider model discovery, and improve setup wizard UX ([#634](#634)) ([2df8d11](2df8d11)) ### Maintenance * bump https://github.com/astral-sh/ruff-pre-commit from v0.15.6 to 0.15.7 ([#628](#628)) ([c641d2c](c641d2c)) * bump python from `584e89d` to `fb83750` in /docker/backend ([#627](#627)) ([1a36eca](1a36eca)) * bump python from `584e89d` to `fb83750` in /docker/sandbox ([#629](#629)) ([fd3e69a](fd3e69a)) * bump the minor-and-patch group across 2 directories with 3 updates ([#630](#630)) ([67d14c4](67d14c4)) * bump the minor-and-patch group with 2 updates ([#631](#631)) ([2e51b60](2e51b60)) * **ci:** add timeout-minutes, harden fuzz script, extend CVE audit ([#626](#626)) ([25420e2](25420e2)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Bug 1 -- Post-startup log loss:
structlog'scache_logger_on_first_use=Truecaused module-level loggers to retain stale processor chains afterconfigure_logging()was called a second time in_bootstrap_app_logging. Changed toFalseso loggers always resolve the current chain. Removed the test conftest monkey-patch that was working around the same issue.Bug 2 -- Provider model discovery: Ollama/LM Studio/vLLM presets shipped with empty
default_models. Added a model auto-discovery service (providers/discovery.py) that queries provider endpoints after preset creation. Ollama uses/api/tags, standard-API providers use/models. AddedPOST /providers/{name}/discover-modelsendpoint for manual retry from the UI. SSRF-safe URL validation applied before outbound requests.Bug 3 -- Setup wizard UX: Back buttons on every step (except Welcome), clickable step indicators for completed steps, step resume on page refresh (backend returns
has_company/has_agentsin status), and guidance when provider has 0 models with a "Retry Discovery" button.Review coverage
Pre-reviewed by 16 specialized agents, 34 findings addressed:
Key fixes from review:
follow_redirects=Falseon httpx client to prevent redirect-based SSRF bypassexcept Exceptioninto specificSettingNotFoundError/JSONDecodeErrorhandlerstimeout-minuteson all 27 CI workflow jobs (accidental removal)setup_models.pyto bringsetup.pyunder 800-line limitTest plan
uv run ruff check src/ tests/-- cleanuv run ruff format src/ tests/-- cleanuv run mypy src/ tests/-- clean (1145 files)uv run python -m pytest tests/ -n auto --cov=synthorg --cov-fail-under=80-- 9732 passed, 93.85%npm --prefix web run lint-- warnings only (pre-existing)npm --prefix web run type-check-- cleannpm --prefix web run test-- 691 passed🤖 Generated with Claude Code
release-as: 0.4.0
Summary by CodeRabbit
New Features
UX
Documentation