can1357/oh-my-pi · error · ValueError
must be a non-empty string
Error message
must be a non-empty string
What it means
Pydantic field validator `_reject_blank` rejects values for `github_token` and `gh_proxy_hmac_key` that are strings containing only whitespace. The library throws this because an empty or whitespace-only secret is never valid — it would silently produce unauthenticated or unsigned requests. It also unwraps pydantic SecretStr values (`get_secret_value`) before checking, so blank secrets wrapped in SecretStr are caught too.
Source
Thrown at python/robomp/src/config.py:435
env_file_encoding="utf-8",
extra="ignore",
case_sensitive=False,
)
github_token: SecretStr = Field(..., alias="GITHUB_TOKEN")
gh_proxy_hmac_key: SecretStr = Field(..., alias="ROBOMP_GH_PROXY_HMAC_KEY")
gh_proxy_bind_host: str = Field("0.0.0.0", alias="ROBOMP_GH_PROXY_BIND_HOST")
gh_proxy_bind_port: int = Field(8081, alias="ROBOMP_GH_PROXY_BIND_PORT")
workspace_root: Path = Field(Path("./data/workspaces"), alias="ROBOMP_WORKSPACE_ROOT")
log_dir: Path = Field(Path("./data/logs"), alias="ROBOMP_LOG_DIR")
gh_proxy_max_body_bytes: int = Field(1 << 20, alias="ROBOMP_GH_PROXY_MAX_BODY_BYTES")
gh_proxy_git_timeout_seconds: float = Field(60.0, alias="ROBOMP_GH_PROXY_GIT_TIMEOUT_SECONDS")
@field_validator("github_token", "gh_proxy_hmac_key", mode="before")
@classmethod
def _reject_blank(cls, value: object) -> object:
if isinstance(value, str) and not value.strip():
raise ValueError("must be a non-empty string")
if hasattr(value, "get_secret_value"):
inner = value.get_secret_value() # type: ignore[attr-defined]
if isinstance(inner, str) and not inner.strip():
raise ValueError("must be a non-empty string")
return value
def load_proxy_settings() -> Settings:
"""Build a `Settings` instance suitable for the gh-proxy process.
Only the env vars the proxy actually consumes are required; the
orchestrator-only fields (webhook secret, bot_login, …) are set to
inert placeholders since `proxy.server` never reads them. Skips the
`Settings()` cross-field validator (which presumes orchestrator
semantics) by routing through `model_construct`.
"""
loader = _ProxyEnvLoader() # type: ignore[call-arg]
return Settings.model_construct(View on GitHub (pinned to 9690622007)
Solutions
- Set the ROBOMP_GITHUB_TOKEN / ROBOMP_GH_PROXY_HMAC_KEY env var (or constructor arg) to a real non-empty value
- Check the .env / secret source actually contains the value — an empty entry in a secrets manager or CI often serializes as ''
- If the field is intentionally unused, omit it entirely rather than passing an empty string (leave it as None/unset if the schema allows)
- Add a startup preflight that logs which specific env var was blank to speed diagnosis
Example fix
// before
Settings(github_token="", gh_proxy_hmac_key=SecretStr(" "))
// after
Settings(github_token="ghp_xxxx", gh_proxy_hmac_key=SecretStr("hmac-secret")) Defensive patterns
Strategy: validation
Validate before calling
def ensure_secret(v):
if v is None: raise ValueError("github_token required")
s = v.get_secret_value() if hasattr(v, "get_secret_value") else v
if not isinstance(s, str) or not s.strip(): raise ValueError("github_token must be a non-empty string")
return v Type guard
def has_secret(v) -> bool:
s = v.get_secret_value() if hasattr(v, "get_secret_value") else v
return isinstance(s, str) and bool(s.strip()) Try / catch
try:
settings = load_proxy_settings()
except ValidationError as exc:
for e in exc.errors():
if "non-empty string" in str(e["msg"]):
print(f"blank secret at {e['loc']}: fix the env/config value")
raise Prevention
- Fail fast at process start: validate Settings before doing any work
- Never default secrets to "" — use None and an explicit is-set check
- In CI, assert required secret vars are non-empty before the job's main steps
- Use a linter/checker that flags `TOKEN=` empty entries in .env files
When it happens
Trigger: Constructing or loading Settings (e.g. via load_proxy_settings or Settings(...)) with `github_token=''`, `github_token=' '`, an env var like ROBOMP_GITHUB_TOKEN set to empty/whitespace, or a SecretStr('') passed in before validation.
Common situations: CI secrets not actually injected (empty env var), `.env` file with `ROBOMP_GITHUB_TOKEN=`, shell default expansion `${TOKEN:-}` resolving to empty, or copying config examples that leave the placeholder blank.
Related errors
- Failed to load tree-sitter language: {err}
- Destination option ${key} must be a string
- Destination option ${key} must be a finite number
- Destination option ${key} must be a boolean
- ${destination} returned an invalid upload URL
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3a271e93a071f71d.
Report an issue: GitHub.