fix(config): resolve dotenv via workspace marker and prefer .mam.env over legacy .env

This commit is contained in:
2026-08-04 21:42:45 +09:00
parent 11583eb173
commit 2458995e75
@@ -33,37 +33,81 @@ import paho.mqtt.client as mqtt
logger = logging.getLogger("delegate_job.mqtt_common") logger = logging.getLogger("delegate_job.mqtt_common")
def _load_dotenv(workspace_dir: str = None) -> None: _warned_deprecated_env = False
"""Load .env file from workspace if it exists and env var not already set. _warned_coexistence_env = False
def _load_dotenv(workspace_dir: Optional[str] = None) -> None:
"""Load .mam.env (or .env fallback) from workspace if it exists.
This ensures Python scripts get the same env vars as the shell wrapper Only sets vars that are not already in os.environ
scripts that source .env. Only sets vars that are not already in os.environ (i.e. OS env takes precedence over env files).
(i.e. OS env takes precedence over .env file).
""" """
import os global _warned_deprecated_env, _warned_coexistence_env
# 1. Check explicit MAM_ENV_FILE override
explicit_file = os.environ.get("MAM_ENV_FILE")
if explicit_file:
if os.path.isfile(explicit_file):
_parse_env_file(explicit_file)
return
# 2. Resolve workspace directory with boundary marker check (.agents or .git)
if workspace_dir is None: if workspace_dir is None:
# Walk up from this script to find workspace root curr = os.path.dirname(os.path.abspath(__file__))
d = os.path.dirname(os.path.abspath(__file__)) resolved_root = None
for _ in range(5): while curr and curr != os.path.dirname(curr):
if os.path.isfile(os.path.join(d, ".env")): if os.path.isdir(os.path.join(curr, ".agents")) or os.path.exists(os.path.join(curr, ".git")):
resolved_root = curr
break break
d = os.path.dirname(d) curr = os.path.dirname(curr)
if not resolved_root:
return
d = resolved_root
else: else:
d = workspace_dir d = workspace_dir
env_path = os.path.join(d, ".env")
if not os.path.isfile(env_path): mam_env_path = os.path.join(d, ".mam.env")
return legacy_env_path = os.path.join(d, ".env")
with open(env_path, "r") as f:
for line in f: has_mam = os.path.isfile(mam_env_path)
line = line.strip() has_legacy = os.path.isfile(legacy_env_path)
if not line or line.startswith("#"):
continue if has_mam and has_legacy:
if "=" in line: if not _warned_coexistence_env:
key, _, val = line.partition("=") logger.warning(
key = key.strip() "Both '%s' and '%s' exist. Loading '%s'. "
val = val.strip().strip('"').strip("'") "Consider removing or migrating '%s'.",
if key and key not in os.environ: mam_env_path, legacy_env_path, mam_env_path, legacy_env_path
os.environ[key] = val )
_warned_coexistence_env = True
_parse_env_file(mam_env_path)
elif has_mam:
_parse_env_file(mam_env_path)
elif has_legacy:
if not _warned_deprecated_env:
logger.warning(
"Loading deprecated config file '%s'. "
"Please migrate to '.mam.env'.",
legacy_env_path
)
_warned_deprecated_env = True
_parse_env_file(legacy_env_path)
def _parse_env_file(path: str) -> None:
try:
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
key, _, val = line.partition("=")
key = key.strip()
val = val.strip().strip('"').strip("'")
if key and key not in os.environ:
os.environ[key] = val
except Exception as e:
logger.warning("Failed to parse env file %s: %s", path, e)
_load_dotenv() _load_dotenv()