forked from molecule-ai/molecule-core
Merge pull request #2763 from Molecule-AI/staging
staging → main: auto-promote 99e7f13
This commit is contained in:
commit
f42feb4ed7
@ -204,17 +204,31 @@ def run_preflight(config: WorkspaceConfig, config_path: str) -> PreflightReport:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
report.failures.append(
|
# Missing required env is a CONFIGURATION issue, not a STRUCTURAL one.
|
||||||
|
# The workspace can still bind /.well-known/agent-card.json — adapter.setup()
|
||||||
|
# raises on the missing key, main.py's PR #2756 try/except mounts the
|
||||||
|
# not-configured JSON-RPC handler, canvas surfaces a clear "agent not
|
||||||
|
# configured: <reason>" error to the user. Hard-failing preflight here
|
||||||
|
# would crash before the not-configured path even loads, leaving the
|
||||||
|
# workspace invisible (the failure mode that bit codex/openclaw bench
|
||||||
|
# 25335853189 on 2026-05-04 even after PR #2756). Warn loudly so logs
|
||||||
|
# remain actionable, but let the boot continue.
|
||||||
|
report.warnings.append(
|
||||||
PreflightIssue(
|
PreflightIssue(
|
||||||
severity="fail",
|
severity="warn",
|
||||||
title="Required env",
|
title="Required env",
|
||||||
detail=f"Missing required environment variable: {env_var}",
|
detail=f"Missing required environment variable: {env_var}",
|
||||||
fix=f"Set {env_var} via the secrets API (global or workspace-level).",
|
fix=(
|
||||||
|
f"Set {env_var} via the secrets API (global or workspace-level). "
|
||||||
|
"Workspace will boot in not-configured state until this is set; "
|
||||||
|
"JSON-RPC will return -32603 'agent not configured' on every request."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Backward compat: if legacy auth_token_file is set, warn but don't block
|
# Backward compat: if legacy auth_token_file is set, warn — same reasoning
|
||||||
# if the token is available via required_env or auth_token_env.
|
# as the required_env block above. The downstream auth check fires inside
|
||||||
|
# adapter.setup(), which is wrapped by main.py's try/except.
|
||||||
token_file = getattr(config.runtime_config, "auth_token_file", "")
|
token_file = getattr(config.runtime_config, "auth_token_file", "")
|
||||||
if token_file:
|
if token_file:
|
||||||
token_path = config_dir / token_file
|
token_path = config_dir / token_file
|
||||||
@ -226,12 +240,16 @@ def run_preflight(config: WorkspaceConfig, config_path: str) -> PreflightReport:
|
|||||||
env_has_token = all(os.environ.get(e) for e in required_env)
|
env_has_token = all(os.environ.get(e) for e in required_env)
|
||||||
|
|
||||||
if not env_has_token:
|
if not env_has_token:
|
||||||
report.failures.append(
|
report.warnings.append(
|
||||||
PreflightIssue(
|
PreflightIssue(
|
||||||
severity="fail",
|
severity="warn",
|
||||||
title="Auth token",
|
title="Auth token",
|
||||||
detail=f"Missing auth token file: {token_file}",
|
detail=f"Missing auth token file: {token_file}",
|
||||||
fix="Remove auth_token_file and use required_env + secrets API instead.",
|
fix=(
|
||||||
|
"Remove auth_token_file and use required_env + secrets API "
|
||||||
|
"instead. Workspace will boot in not-configured state until "
|
||||||
|
"the token is provided."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -225,8 +225,14 @@ def test_required_env_present_passes(tmp_path, monkeypatch):
|
|||||||
assert not any(issue.title == "Required env" for issue in report.failures)
|
assert not any(issue.title == "Required env" for issue in report.failures)
|
||||||
|
|
||||||
|
|
||||||
def test_required_env_missing_fails(tmp_path, monkeypatch):
|
def test_required_env_missing_warns_does_not_fail(tmp_path, monkeypatch):
|
||||||
"""When a required_env var is missing, preflight fails."""
|
"""When a required_env var is missing, preflight WARNS but does not
|
||||||
|
fail the boot. Pairs with PR #2756 (molecule-core): the workspace
|
||||||
|
binds /.well-known/agent-card.json regardless of credentials and
|
||||||
|
routes JSON-RPC to a -32603 'agent not configured' handler. Hard
|
||||||
|
failing here would crash before the not-configured path even loads,
|
||||||
|
leaving the workspace invisible — that's the failure mode that bit
|
||||||
|
codex/openclaw bench 25335853189 on 2026-05-04 even after PR #2756."""
|
||||||
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
||||||
|
|
||||||
config = make_config(
|
config = make_config(
|
||||||
@ -236,10 +242,13 @@ def test_required_env_missing_fails(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
report = run_preflight(config, str(tmp_path))
|
report = run_preflight(config, str(tmp_path))
|
||||||
|
|
||||||
assert report.ok is False
|
assert report.ok is True
|
||||||
assert any(
|
assert any(
|
||||||
issue.title == "Required env" and "CLAUDE_CODE_OAUTH_TOKEN" in issue.detail
|
issue.title == "Required env" and "CLAUDE_CODE_OAUTH_TOKEN" in issue.detail
|
||||||
for issue in report.failures
|
for issue in report.warnings
|
||||||
|
)
|
||||||
|
assert not any(
|
||||||
|
issue.title == "Required env" for issue in report.failures
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -257,8 +266,11 @@ def test_required_env_multiple_all_present_passes(tmp_path, monkeypatch):
|
|||||||
assert report.ok is True
|
assert report.ok is True
|
||||||
|
|
||||||
|
|
||||||
def test_required_env_multiple_one_missing_fails(tmp_path, monkeypatch):
|
def test_required_env_multiple_one_missing_warns(tmp_path, monkeypatch):
|
||||||
"""If any required_env var is missing, preflight fails with that var named."""
|
"""If any required_env var is missing, preflight warns with that var
|
||||||
|
named (and does NOT fail). The eventual setup() failure is what
|
||||||
|
actually surfaces to the user via the -32603 handler — preflight is
|
||||||
|
just a logging signal for operators inspecting boot logs."""
|
||||||
monkeypatch.setenv("API_KEY_A", "key-a")
|
monkeypatch.setenv("API_KEY_A", "key-a")
|
||||||
monkeypatch.delenv("API_KEY_B", raising=False)
|
monkeypatch.delenv("API_KEY_B", raising=False)
|
||||||
|
|
||||||
@ -268,10 +280,10 @@ def test_required_env_multiple_one_missing_fails(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
report = run_preflight(config, str(tmp_path))
|
report = run_preflight(config, str(tmp_path))
|
||||||
|
|
||||||
assert report.ok is False
|
assert report.ok is True
|
||||||
assert any(
|
assert any(
|
||||||
issue.title == "Required env" and "API_KEY_B" in issue.detail
|
issue.title == "Required env" and "API_KEY_B" in issue.detail
|
||||||
for issue in report.failures
|
for issue in report.warnings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -317,8 +329,10 @@ def test_required_env_skipped_in_smoke_mode(tmp_path, monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_required_env_smoke_mode_off_still_fails(tmp_path, monkeypatch):
|
def test_required_env_smoke_mode_off_still_warns(tmp_path, monkeypatch):
|
||||||
"""Sanity: smoke bypass is OFF when MOLECULE_SMOKE_MODE is unset."""
|
"""Sanity: smoke bypass is OFF when MOLECULE_SMOKE_MODE is unset, but
|
||||||
|
the warning still fires (and preflight no longer hard-fails — see
|
||||||
|
test_required_env_missing_warns_does_not_fail for the rationale)."""
|
||||||
monkeypatch.delenv("HERMES_API_KEY", raising=False)
|
monkeypatch.delenv("HERMES_API_KEY", raising=False)
|
||||||
monkeypatch.delenv("MOLECULE_SMOKE_MODE", raising=False)
|
monkeypatch.delenv("MOLECULE_SMOKE_MODE", raising=False)
|
||||||
|
|
||||||
@ -328,10 +342,13 @@ def test_required_env_smoke_mode_off_still_fails(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
report = run_preflight(config, str(tmp_path))
|
report = run_preflight(config, str(tmp_path))
|
||||||
|
|
||||||
assert report.ok is False
|
assert report.ok is True
|
||||||
assert any(
|
assert any(
|
||||||
issue.title == "Required env" and "HERMES_API_KEY" in issue.detail
|
issue.title == "Required env" and "HERMES_API_KEY" in issue.detail
|
||||||
for issue in report.failures
|
for issue in report.warnings
|
||||||
|
)
|
||||||
|
assert not any(
|
||||||
|
issue.title == "Required env" for issue in report.failures
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -383,10 +400,12 @@ def test_top_level_required_env_used_when_no_models_declared(tmp_path, monkeypat
|
|||||||
|
|
||||||
report = run_preflight(config, str(tmp_path))
|
report = run_preflight(config, str(tmp_path))
|
||||||
|
|
||||||
assert report.ok is False
|
# Missing required_env is now a warning (workspace boots in
|
||||||
|
# not-configured state); see test_required_env_missing_warns_does_not_fail.
|
||||||
|
assert report.ok is True
|
||||||
assert any(
|
assert any(
|
||||||
issue.title == "Required env" and "CLAUDE_CODE_OAUTH_TOKEN" in issue.detail
|
issue.title == "Required env" and "CLAUDE_CODE_OAUTH_TOKEN" in issue.detail
|
||||||
for issue in report.failures
|
for issue in report.warnings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -411,10 +430,10 @@ def test_top_level_used_when_picked_model_not_in_models_list(tmp_path, monkeypat
|
|||||||
|
|
||||||
report = run_preflight(config, str(tmp_path))
|
report = run_preflight(config, str(tmp_path))
|
||||||
|
|
||||||
assert report.ok is False
|
assert report.ok is True
|
||||||
assert any(
|
assert any(
|
||||||
issue.title == "Required env" and "CLAUDE_CODE_OAUTH_TOKEN" in issue.detail
|
issue.title == "Required env" and "CLAUDE_CODE_OAUTH_TOKEN" in issue.detail
|
||||||
for issue in report.failures
|
for issue in report.warnings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -526,8 +545,13 @@ def test_per_model_required_env_null_treated_as_empty_no_auth(tmp_path, monkeypa
|
|||||||
# ---------- Legacy auth_token_file backward compat ----------
|
# ---------- Legacy auth_token_file backward compat ----------
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_auth_token_file_missing_no_env_fails(tmp_path, monkeypatch):
|
def test_legacy_auth_token_file_missing_no_env_warns(tmp_path, monkeypatch):
|
||||||
"""Legacy: missing auth_token_file with no env var should fail."""
|
"""Legacy: missing auth_token_file with no env var emits a warning,
|
||||||
|
not a hard failure. Same reasoning as
|
||||||
|
test_required_env_missing_warns_does_not_fail — adapter.setup() is
|
||||||
|
the authoritative auth check, preflight just surfaces the issue
|
||||||
|
early in the boot log. The workspace still binds /agent-card and
|
||||||
|
routes to the not-configured -32603 handler."""
|
||||||
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
||||||
|
|
||||||
config = make_config(
|
config = make_config(
|
||||||
@ -536,8 +560,9 @@ def test_legacy_auth_token_file_missing_no_env_fails(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
report = run_preflight(config, str(tmp_path))
|
report = run_preflight(config, str(tmp_path))
|
||||||
|
|
||||||
assert report.ok is False
|
assert report.ok is True
|
||||||
assert any(issue.title == "Auth token" for issue in report.failures)
|
assert any(issue.title == "Auth token" for issue in report.warnings)
|
||||||
|
assert not any(issue.title == "Auth token" for issue in report.failures)
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_auth_token_file_missing_but_auth_token_env_passes(tmp_path, monkeypatch):
|
def test_legacy_auth_token_file_missing_but_auth_token_env_passes(tmp_path, monkeypatch):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user