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

  1. Set the ROBOMP_GITHUB_TOKEN / ROBOMP_GH_PROXY_HMAC_KEY env var (or constructor arg) to a real non-empty value
  2. Check the .env / secret source actually contains the value — an empty entry in a secrets manager or CI often serializes as ''
  3. If the field is intentionally unused, omit it entirely rather than passing an empty string (leave it as None/unset if the schema allows)
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3a271e93a071f71d. Report an issue: GitHub.