Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2e30f9b
fix(config): close 6 settings reachability + kill-switch gaps (#1776)
Aureliolo May 7, 2026
e611600
refactor: address pre-PR review findings
Aureliolo May 8, 2026
829c422
fix: babysit round 1, 11 findings (8 coderabbit, 2 gemini, 1 dispatch…
Aureliolo May 8, 2026
caa0169
fix: babysit round 2, 3 findings (3 coderabbit)
Aureliolo May 8, 2026
93ca850
fix: babysit round 3, 2 findings (2 coderabbit)
Aureliolo May 8, 2026
86241b8
fix: babysit round 4, 2 findings (2 coderabbit)
Aureliolo May 8, 2026
805783b
fix: babysit round 5, 7 findings (7 coderabbit) + sweep error_desc
Aureliolo May 8, 2026
ed34bcb
chore: refresh baselines after error_desc sweep line shifts
Aureliolo May 8, 2026
cedbf02
fix: babysit round 6, 5 findings (5 coderabbit) + CRUD refactor
Aureliolo May 8, 2026
891861f
fix: babysit round 7, 7 findings (7 coderabbit)
Aureliolo May 8, 2026
fb4f017
fix: babysit round 8, 6 findings (6 coderabbit)
Aureliolo May 8, 2026
31fbb2f
fix: babysit round 9, 7 findings (7 coderabbit)
Aureliolo May 8, 2026
b6f90d0
fix: babysit round 10, 10 findings (10 coderabbit)
Aureliolo May 8, 2026
556367b
fix: babysit round 11, 7 findings (7 coderabbit)
Aureliolo May 8, 2026
a5d5938
fix: babysit round 12, 2 findings (2 coderabbit)
Aureliolo May 8, 2026
a285bc4
fix: babysit round 13, 4 findings (4 coderabbit)
Aureliolo May 8, 2026
6a3c592
fix: babysit round 14, kill-switch fixes for new loops on main
Aureliolo May 8, 2026
0f45cf2
fix: babysit round 15, 7 findings (7 coderabbit)
Aureliolo May 8, 2026
0d85128
fix: babysit round 16, 2 findings (2 coderabbit)
Aureliolo May 8, 2026
2b7e120
fix: babysit round 17, 1 finding (1 coderabbit)
Aureliolo May 8, 2026
15985c4
fix: babysit round 18 kill-switches
Aureliolo May 8, 2026
a52a528
fix: babysit round 19, 8 CR findings + codecov patch coverage
Aureliolo May 8, 2026
7f5e800
fix: babysit round 19, gate-driven follow-ups
Aureliolo May 8, 2026
7eff0e0
fix: babysit round 20, 12 CR findings (12 coderabbit, 0 ci)
Aureliolo May 8, 2026
60731ff
fix: babysit round 20, magic-numbers gate follow-up
Aureliolo May 8, 2026
c0a8033
fix: babysit round 21, 6 CR findings (6 coderabbit, 0 ci)
Aureliolo May 8, 2026
56f750c
fix: babysit round 22, 8 CR findings (8 coderabbit, 0 ci)
Aureliolo May 9, 2026
33bc33f
fix: babysit round 23, 3 CR findings (3 coderabbit, 0 ci)
Aureliolo May 9, 2026
1016cdf
fix: babysit round 24, 3 CR findings (3 coderabbit, 0 ci)
Aureliolo May 9, 2026
90c0385
fix: babysit round 24, kill-switch gate follow-up
Aureliolo May 9, 2026
554e470
fix: babysit round 26, 5 CR findings (5 coderabbit, 0 ci)
Aureliolo May 9, 2026
7e46862
fix: babysit round 27, 1 CR finding (1 valid, 1 skipped)
Aureliolo May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ jobs:
- name: Migration-framing / phase-N gate (src/ + tests/)
run: uv run python scripts/check_no_migration_framing.py

- name: Long-running-loop kill-switch gate (src/synthorg)
run: uv run python scripts/check_long_running_loops_have_kill_switch.py
Comment on lines +149 to +150

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== setup-python-uv action definition =="
sed -n '1,220p' .github/actions/setup-python-uv/action.yml

echo
echo "== workflow call sites =="
rg -n -C2 'setup-python-uv|python-version' .github/workflows/ci.yml .github/actions/setup-python-uv/action.yml

Repository: Aureliolo/synthorg

Length of output: 5959


The lint job should explicitly declare Python 3.14 for clarity, even though the composite action default is already set correctly.

The setup-python-uv action defaults to Python 3.14, so this gate will currently work as intended. However, explicitly passing python-version: "3.14" to the action in this job makes the dependency on that specific version explicit and defends against future accidental default drift in the composite action.

Composite action default (verified)

The .github/actions/setup-python-uv/action.yml defines:

inputs:
  python-version:
    description: Python version to install
    required: false
    default: "3.14"
🤖 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 @.github/workflows/ci.yml around lines 149 - 150, The workflow job
"Long-running-loop kill-switch gate (src/synthorg)" uses the setup-python-uv
composite action but doesn't explicitly pass the Python version; update the job
to call the setup-python-uv action with python-version: "3.14" so the job
invocation explicitly sets Python 3.14 (i.e., add python-version: "3.14" to the
setup-python-uv step that runs
scripts/check_long_running_loops_have_kill_switch.py).


# ── Docs: Doc-claim drift gate ──
# Runs on python OR doc_claims so a docs-only PR that bumps a guarded
# claim (eg "100+ event constant modules") still triggers the gate.
Expand Down
16 changes: 15 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ ci:
# scripts with no CI counterpart, and letting pre-commit.ci enforce
# them closes the gap where a PR from a contributor who skipped local
# hooks would otherwise introduce a regression unchecked.
skip: [commitizen, gitleaks, hadolint-docker, caddy-validate, zizmor, no-em-dashes, no-redundant-timeout, mypy, pytest-unit, golangci-lint, go-vet, go-test, eslint-web, check-push-rebased, check-single-migration-per-pr, check-no-modify-migration, forbidden-literals, persistence-boundary, persistence-protocol-uniformity, dependency-inversion, provider-complete-chokepoint, no-new-logger-exception-str-exc, otlp-span-redaction, orphan-fixtures, doc-drift-counts, boundary-typed, setting-to-startup-trace, list-pagination, domain-error-hierarchy, dead-api-endpoints, dual-backend-test-parity, schema-drift, no-magic-numbers, convention-gate-inventory, mcp-admin-guardrail]
skip: [commitizen, gitleaks, hadolint-docker, caddy-validate, zizmor, no-em-dashes, no-redundant-timeout, mypy, pytest-unit, golangci-lint, go-vet, go-test, eslint-web, check-push-rebased, check-single-migration-per-pr, check-no-modify-migration, forbidden-literals, persistence-boundary, persistence-protocol-uniformity, dependency-inversion, provider-complete-chokepoint, no-new-logger-exception-str-exc, otlp-span-redaction, orphan-fixtures, doc-drift-counts, boundary-typed, setting-to-startup-trace, long-running-loop-kill-switch, list-pagination, domain-error-hierarchy, dead-api-endpoints, dual-backend-test-parity, schema-drift, no-magic-numbers, convention-gate-inventory, mcp-admin-guardrail]

default_install_hook_types: [pre-commit, commit-msg, pre-push]

Expand Down Expand Up @@ -399,6 +399,20 @@ repos:
pass_filenames: false
stages: [pre-push]

- id: long-running-loop-kill-switch
name: long-running loops carry a runtime kill-switch
entry: uv run python scripts/check_long_running_loops_have_kill_switch.py
language: system
# Trigger on any change to src/synthorg Python files, the gate
# script itself, or its baseline. A PR that adds a new
# ``while True:`` / ``while not stop_event.is_set():`` async
# loop without a per-iteration ``ConfigResolver.get_bool(...
# _enabled)`` re-read (or a mandatory-justification opt-out)
# cannot bypass the check by editing only the source file.
files: ^(src/synthorg/.*\.py|scripts/check_long_running_loops_have_kill_switch\.py|scripts/long_running_loops_kill_switch_baseline\.txt|\.pre-commit-config\.yaml)$
pass_filenames: false
stages: [pre-push]

- id: domain-error-hierarchy
name: domain-error-hierarchy gate (DomainError-rooted exception classes)
entry: uv run python scripts/check_domain_error_hierarchy.py
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Existing gate inventory (all under `scripts/`):
- `check_forbidden_literals.py`
- `check_list_pagination.py`
- `check_logger_exception_str_exc.py`
- `check_long_running_loops_have_kill_switch.py`
- `check_mcp_admin_tool_guardrails.py`
- `check_mock_spec.py`
- `check_doc_numeric_macros.py`
Expand Down
4 changes: 2 additions & 2 deletions cli/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The CLI uses four hint tiers with different visibility rules per `hints` mode. W
- **Backend / channel overrides**: `SYNTHORG_LOG_LEVEL`, `SYNTHORG_BACKEND_PORT`, `SYNTHORG_WEB_PORT`, `SYNTHORG_CHANNEL`, `SYNTHORG_IMAGE_TAG`, `SYNTHORG_TELEMETRY_ENABLED`, `SYNTHORG_AUTO_*` (UPDATE_CLI / PULL / RESTART).
- **Image / registry overrides**: `SYNTHORG_REGISTRY_HOST`, `SYNTHORG_IMAGE_REPO_PREFIX`, `SYNTHORG_DHI_REGISTRY`, `SYNTHORG_POSTGRES_IMAGE_TAG`, `SYNTHORG_NATS_IMAGE_TAG`, `SYNTHORG_FINE_TUNE_IMAGE` (any of these disables verification for that invocation). `SYNTHORG_POSTGRES_IMAGE_TAG` and `SYNTHORG_NATS_IMAGE_TAG` default to `Default{Postgres,NATS}ImageTag` in `cli/internal/config/state.go`, with the matching multi-arch index digest stored as a sibling `Default{Postgres,NATS}ImageDigest` constant in the same file. Both are kept current by a single Renovate customManager (one regex match per dep, capturing tag + digest together) that watches the `// renovate:` annotation on the tag constant. `cli/internal/verify/dhi.go` derives its `dhiPinnedIndexDigests` map from these constants at init -- state.go is the single source of truth. Renovate's docker-compose manager is disabled on `docker/compose.yml`, so any PR bumping `Default{Postgres,NATS}Image{Tag,Digest}` MUST hand-mirror the matching `image:` line in `docker/compose.yml` in the same commit; `cli/internal/verify/compose_sync_test.go` enforces this and fails the build on drift.
- **Timeouts and retry tuning**: `SYNTHORG_BACKUP_*_TIMEOUT`, `SYNTHORG_HEALTH_CHECK_TIMEOUT`, `SYNTHORG_SELF_UPDATE_*_TIMEOUT`, `SYNTHORG_TUF_FETCH_TIMEOUT`, `SYNTHORG_ATTESTATION_HTTP_TIMEOUT`, `SYNTHORG_IMAGE_VERIFY_TIMEOUT` (default 120s, hard min 1s), `SYNTHORG_IMAGE_PULL_ATTEMPTS` (1..100, default 3), `SYNTHORG_IMAGE_PULL_RETRY_DELAY` (default 2s, exponential).
- **Byte caps and ports**: `SYNTHORG_MAX_API_RESPONSE_BYTES` (default 4MiB), `SYNTHORG_MAX_BINARY_BYTES` (256MiB), `SYNTHORG_MAX_ARCHIVE_ENTRY_BYTES` (128MiB), `SYNTHORG_DEFAULT_NATS_URL`, `SYNTHORG_DEFAULT_NATS_STREAM_PREFIX`, `SYNTHORG_FINE_TUNE_HEALTH_PORT` (env-only, not in `config set`).
- **Byte caps and ports**: `SYNTHORG_MAX_API_RESPONSE_BYTES` (default 4MiB), `SYNTHORG_MAX_BINARY_BYTES` (256MiB), `SYNTHORG_MAX_ARCHIVE_ENTRY_BYTES` (128MiB), `SYNTHORG_NATS_URL` (single source of truth shared with the backend's `communication.nats_url` setting; env-only, not in `config set`), `SYNTHORG_DEFAULT_NATS_STREAM_PREFIX`, `SYNTHORG_FINE_TUNE_HEALTH_PORT` (env-only, not in `config set`).

See [docs/reference/cli-env-vars.md](../docs/reference/cli-env-vars.md) for the full table with descriptions, byte-cap rationales, and the audit rationale for hardcoded `localhost` / `postgres:5432` / `nats:4222` literals (correct by design).

Expand Down Expand Up @@ -116,7 +116,7 @@ See [docs/reference/cli-config-subcommands.md](../docs/reference/cli-config-subc
| `backup restore` | `--confirm` (required), `--dry-run`, `--no-restart`, `--timeout` |
| `completion` | `[bash \| zsh \| fish \| powershell]`: emit shell autocompletion script (Cobra built-in) |
| `completion-install` | `[bash \| zsh \| fish \| powershell]`: write the autocompletion script into your shell startup (`~/.bashrc`, `~/.zshrc`, etc.) |
| `worker start` | `--workers` (int, default 4), `--nats-url`, `--stream-prefix`, `--container` (flag default `""`; falls back to `synthorg-backend` when unset): runs the distributed task-queue worker pool |
| `worker start` | `--workers` (int, default 4), `--nats-url` (precedence: flag > `SYNTHORG_NATS_URL` env > compiled default), `--stream-prefix`, `--container` (flag default `""`; falls back to `synthorg-backend` when unset): runs the distributed task-queue worker pool |
| `new <kind> <domain>` | `--dry-run`, `--overwrite`: scaffolds a conventions-clean Python file set under `src/synthorg/` for a new feature. `<kind>` is one of `service` / `persistence` / `tool` / `controller`. See [docs/reference/scaffolding.md](../docs/reference/scaffolding.md). |
| `wipe` | `--dry-run`, `--no-backup`, `--keep-images` |
| `doctor` | `--checks`, `--fix` |
Expand Down
18 changes: 10 additions & 8 deletions cli/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var supportedConfigKeys = []string{
"backend_port",
"backup_create_timeout", "backup_restore_timeout",
"changelog_view", "channel", "color",
"default_nats_stream_prefix", "default_nats_url",
"default_nats_stream_prefix",
"dhi_registry", "docker_sock",
"fine_tuning", "fine_tuning_variant",
"health_check_timeout",
Expand Down Expand Up @@ -101,8 +101,9 @@ Supported keys:
timestamps Timestamp display mode
web_port Web dashboard port

Plus 17 runtime tunables (registry host, image tags, timeouts, size
limits, NATS defaults). See cli/CLAUDE.md for the full list.`,
Plus the runtime tunables (registry host, image tags, timeouts, size
limits, NATS defaults). Run 'synthorg config list' to see every
settable key with its current value.`,
Example: ` synthorg config get backend_port
synthorg config get channel
synthorg config get image_tag`,
Expand Down Expand Up @@ -139,12 +140,14 @@ Supported keys:
timestamps Timestamp format: "relative" or "iso8601"
web_port Web dashboard port: 1-65535

Plus 17 runtime tunables (registry_host, image_repo_prefix, dhi_registry,
postgres_image_tag, nats_image_tag, default_nats_url,
Plus 16 runtime tunables (registry_host, image_repo_prefix, dhi_registry,
postgres_image_tag, nats_image_tag,
default_nats_stream_prefix, backup_create_timeout, backup_restore_timeout,
health_check_timeout, self_update_http_timeout, self_update_api_timeout,
tuf_fetch_timeout, attestation_http_timeout, max_api_response_bytes,
max_binary_bytes, max_archive_entry_bytes). See cli/CLAUDE.md for formats.
max_binary_bytes, max_archive_entry_bytes). Run 'synthorg config list'
for the full key set with current values; durations accept Go duration
strings ("30s", "5m"); byte sizes accept "4MiB", "256MB", etc.

Keys that affect Docker compose (backend_port, web_port, sandbox, docker_sock,
image_tag, log_level, telemetry_opt_in, fine_tuning, fine_tuning_variant, and
Expand Down Expand Up @@ -317,7 +320,7 @@ var gettableConfigKeys = []string{
"backend_port",
"backup_create_timeout", "backup_restore_timeout",
"changelog_view", "channel", "color",
"default_nats_stream_prefix", "default_nats_url",
"default_nats_stream_prefix",
"dhi_registry", "docker_sock",
"fine_tuning", "fine_tuning_variant",
"health_check_timeout",
Expand Down Expand Up @@ -621,7 +624,6 @@ var composeAffectingKeys = map[string]bool{
"dhi_registry": true,
"postgres_image_tag": true,
"nats_image_tag": true,
"default_nats_url": true,
"default_nats_stream_prefix": true,
"fine_tuning": true,
"fine_tuning_variant": true,
Expand Down
17 changes: 0 additions & 17 deletions cli/cmd/config_tunables.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ func applyTunableConfigValue(state *config.State, key, value string) (bool, erro
return true, setTag(value, "postgres_image_tag", &state.PostgresImageTag)
case "nats_image_tag":
return true, setTag(value, "nats_image_tag", &state.NATSImageTag)
case "default_nats_url":
return true, setNATSURL(value, &state.DefaultNATSURL)
case "default_nats_stream_prefix":
return true, setStreamPrefix(value, &state.DefaultNATSStreamPrefix)
case "backup_create_timeout":
Expand Down Expand Up @@ -84,8 +82,6 @@ func resetTunableConfigValue(state *config.State, key string) bool {
state.PostgresImageTag = ""
case "nats_image_tag":
state.NATSImageTag = ""
case "default_nats_url":
state.DefaultNATSURL = ""
case "default_nats_stream_prefix":
state.DefaultNATSStreamPrefix = ""
case "backup_create_timeout":
Expand Down Expand Up @@ -135,8 +131,6 @@ func tunableConfigGetValue(state config.State, key string) (string, bool) {
return displayOrFallback(state.PostgresImageTag, config.DefaultPostgresImageTag), true
case "nats_image_tag":
return displayOrFallback(state.NATSImageTag, config.DefaultNATSImageTag), true
case "default_nats_url":
return displayOrFallback(state.DefaultNATSURL, config.DefaultNATSURLValue), true
case "default_nats_stream_prefix":
return displayOrFallback(state.DefaultNATSStreamPrefix, config.DefaultNATSStreamPrefixValue), true
case "backup_create_timeout":
Expand Down Expand Up @@ -183,8 +177,6 @@ func tunableEnvVarForKey(key string) string {
return EnvPostgresImageTag
case "nats_image_tag":
return EnvNATSImageTag
case "default_nats_url":
return EnvDefaultNATSURL
case "default_nats_stream_prefix":
return EnvDefaultNATSStreamPfx
case "backup_create_timeout":
Expand Down Expand Up @@ -246,15 +238,6 @@ func setTag(value, key string, target *string) error {
return nil
}

// setNATSURL validates a NATS URL and writes it into target.
func setNATSURL(value string, target *string) error {
if err := config.ValidateNATSURL(value); err != nil {
return fmt.Errorf("invalid default_nats_url %q: %w", value, err)
}
*target = value
return nil
}

// setStreamPrefix validates a NATS JetStream stream prefix.
func setStreamPrefix(value string, target *string) error {
if !config.IsValidStreamPrefix(value) {
Expand Down
38 changes: 35 additions & 3 deletions cli/cmd/config_tunables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ var tunableKeys = []struct {
{"dhi_registry", "private.docker.example"},
{"postgres_image_tag", "17-debian13"},
{"nats_image_tag", "2.11-debian13"},
{"default_nats_url", "nats://example.com:4222"},
{"default_nats_stream_prefix", "CUSTOM"},
{"backup_create_timeout", "90s"},
{"backup_restore_timeout", "45s"},
Expand Down Expand Up @@ -91,7 +90,6 @@ func TestTunableKeys_InvalidValues(t *testing.T) {
"dhi_registry": "invalid!host",
"postgres_image_tag": "-leading-dash",
"nats_image_tag": "with space",
"default_nats_url": "http://example.com",
"default_nats_stream_prefix": "lowercase",
"backup_create_timeout": "not-a-duration",
"health_check_timeout": "-5s",
Expand All @@ -112,11 +110,45 @@ func TestTunableKeys_ComposeAffectingSet(t *testing.T) {
want := []string{
"registry_host", "image_repo_prefix", "dhi_registry",
"postgres_image_tag", "nats_image_tag",
"default_nats_url", "default_nats_stream_prefix",
"default_nats_stream_prefix",
}
for _, k := range want {
if !composeAffectingKeys[k] {
t.Errorf("%s should be in composeAffectingKeys", k)
}
}
// SYNTHORG_NATS_URL is env-only since the parallel CLI tunable
// layer was removed; it must not creep back in as a tunable.
if composeAffectingKeys["default_nats_url"] {
t.Errorf("default_nats_url should NOT be in composeAffectingKeys")
}
}

// TestRemovedTunable_DefaultNATSURLRejected guards against re-introducing
// "default_nats_url" as a config-set tunable. SYNTHORG_NATS_URL is the
// single env-only source of truth shared with the backend; if a future
// PR mistakenly re-adds the parallel CLI tunable, this test fails by
// enumerating every entry point (key list + apply + reset).
func TestRemovedTunable_DefaultNATSURLRejected(t *testing.T) {
const removedKey = "default_nats_url"
state := config.DefaultState()

if slices.Contains(supportedConfigKeys, removedKey) {
t.Errorf("%s should NOT be present in supportedConfigKeys", removedKey)
}
if slices.Contains(gettableConfigKeys, removedKey) {
t.Errorf("%s should NOT be present in gettableConfigKeys", removedKey)
}
if err := applyConfigValue(&state, removedKey, "nats://example:4222"); err == nil {
t.Errorf("applyConfigValue should reject removed key %q", removedKey)
}
if err := resetConfigValue(&state, removedKey); err == nil {
t.Errorf("resetConfigValue should reject removed key %q", removedKey)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// envVarForKey backs the env-var plumbing the live tunables use;
// ensuring it returns "" prevents the removed key from lingering
// as a back-channel even after the apply/reset paths reject it.
if env := envVarForKey(removedKey); env != "" {
t.Errorf("envVarForKey(%q) = %q, want \"\" (no env-var mapping)", removedKey, env)
}
}
1 change: 0 additions & 1 deletion cli/cmd/envvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const (
EnvDHIRegistry = config.EnvDHIRegistry
EnvPostgresImageTag = config.EnvPostgresImageTag
EnvNATSImageTag = config.EnvNATSImageTag
EnvDefaultNATSURL = config.EnvDefaultNATSURL
EnvDefaultNATSStreamPfx = config.EnvDefaultNATSStreamPfx
EnvBackupCreateTimeout = config.EnvBackupCreateTimeout
EnvBackupRestoreTimeout = config.EnvBackupRestoreTimeout
Expand Down
42 changes: 31 additions & 11 deletions cli/cmd/worker_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,49 @@ func runWorkerStart(cmd *cobra.Command, _ []string) error {
opts := GetGlobalOpts(cmd.Context())
out := ui.NewUIWithOptions(cmd.OutOrStdout(), opts.UIOptions())

// Precedence: explicit flag > env/config (via Tunables) > flag default.
if !cmd.Flags().Changed("nats-url") {
workerStartNatsURL = opts.Tunables.DefaultNATSURL
// Precedence for --nats-url: explicit flag > SYNTHORG_NATS_URL env >
// compiled-in fallback. Single source of truth shared with the
// backend's ``communication.nats_url`` setting; no parallel
// CLI-only tunable layer.
//
// Reused-process safety: pflag's StringVar binds to a package
// global. A previous explicit ``--nats-url=foo`` invocation
// leaves ``workerStartNatsURL == "foo"`` after the command
// returns, so a later invocation that omits the flag would
// inherit ``"foo"`` instead of falling back to the compiled
// default. Initialise the omitted-flag branch from
// ``config.DefaultNATSURLValue`` directly so the env override
// (or the documented default) wins, regardless of what the
// previous invocation passed.
var resolvedNATSURL string
if cmd.Flags().Changed("nats-url") {
resolvedNATSURL = workerStartNatsURL
} else {
resolvedNATSURL = config.DefaultNATSURLValue
if envURL := strings.TrimSpace(os.Getenv("SYNTHORG_NATS_URL")); envURL != "" {
resolvedNATSURL = envURL
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
resolvedStreamPrefix := workerStartStreamPrefix
if !cmd.Flags().Changed("stream-prefix") {
workerStartStreamPrefix = opts.Tunables.DefaultNATSStreamPrefix
resolvedStreamPrefix = opts.Tunables.DefaultNATSStreamPrefix
}

if workerStartCount <= 0 {
return fmt.Errorf("--workers must be > 0, got %d", workerStartCount)
}
if err := validateNatsURL(workerStartNatsURL); err != nil {
if err := validateNatsURL(resolvedNATSURL); err != nil {
return err
}
// Validate the stream prefix up front so an explicit --stream-prefix
// with bad characters is rejected here rather than deep inside the
// worker pool. The tunable path already runs through the same regex
// via config.ResolveTunables, but an explicit flag skips that check
// and would otherwise end up as an env var on the backend process.
if !config.IsValidStreamPrefix(workerStartStreamPrefix) {
if !config.IsValidStreamPrefix(resolvedStreamPrefix) {
return fmt.Errorf(
"invalid --stream-prefix %q: must match [A-Z0-9][A-Z0-9_-]*",
workerStartStreamPrefix,
resolvedStreamPrefix,
)
}
if err := validateContainerName(workerStartContainer); err != nil {
Expand Down Expand Up @@ -116,14 +136,14 @@ func runWorkerStart(cmd *cobra.Command, _ []string) error {
"--workers", strconv.Itoa(workerStartCount),
}
env := append(os.Environ(),
"SYNTHORG_NATS_URL="+workerStartNatsURL,
"SYNTHORG_NATS_STREAM_PREFIX="+workerStartStreamPrefix,
"SYNTHORG_NATS_URL="+resolvedNATSURL,
"SYNTHORG_NATS_STREAM_PREFIX="+resolvedStreamPrefix,
"SYNTHORG_WORKER_COUNT="+strconv.Itoa(workerStartCount),
)

out.KeyValue("Workers", strconv.Itoa(workerStartCount))
out.KeyValue("NATS URL", redactNatsURL(workerStartNatsURL))
out.KeyValue("Stream prefix", workerStartStreamPrefix)
out.KeyValue("NATS URL", redactNatsURL(resolvedNATSURL))
out.KeyValue("Stream prefix", resolvedStreamPrefix)
out.KeyValue("Container", container)
out.HintNextStep("Press Ctrl+C to stop workers.")

Expand Down
Loading
Loading