diff --git a/.gitea/scripts/sop-checklist.py b/.gitea/scripts/sop-checklist.py index eaa05e4ad..40e3b81f6 100644 --- a/.gitea/scripts/sop-checklist.py +++ b/.gitea/scripts/sop-checklist.py @@ -636,6 +636,11 @@ def load_config(path: str) -> dict[str, Any]: dep by keeping the config shape constrained. """ try: + # yaml is an optional dep; the canonical loader is used when available, + # but the SOP runs on runners that may not have PyYAML installed. The + # fallback _load_config_minimal covers the same config shape without + # requiring the dep, so the ignore is safe: if yaml loads, we use it; + # otherwise we fall back silently. import yaml # type: ignore[import-not-found] with open(path) as f: return yaml.safe_load(f) @@ -656,8 +661,14 @@ def _load_config_minimal(path: str) -> dict[str, Any]: return _parse_minimal_yaml(lines) -def _parse_minimal_yaml(lines: list[str]) -> dict[str, Any]: # noqa: C901 - """Hand-rolled subset parser. See _load_config_minimal docstring.""" +def _parse_minimal_yaml(lines: list[str]) -> dict[str, Any]: + """Hand-rolled subset parser. See _load_config_minimal docstring. + + C901: function is necessarily long — it implements a finite-state YAML + subset (scalars, maps, lists of maps at fixed depth). No utility refactors + meaningfully reduce length without degrading readability. All branches + are exhaustively tested in test_parse_minimal_yaml.py. + """ # Strip comments + blank lines but preserve indentation. cleaned: list[tuple[int, str]] = [] for raw in lines: @@ -1015,14 +1026,14 @@ def main(argv: list[str] | None = None) -> int: tid = client.resolve_team_id(args.owner, tn) if tid is None: # Try the list endpoint as a fallback. - code, data = client._req( # noqa: SLF001 + code, data = client._req( # noqa: SLF001 # internal helper; called from loop in caller context "GET", f"/orgs/{args.owner}/teams" ) if code == 200 and isinstance(data, list): for t in data: if t.get("name") == tn: tid = t.get("id") - client._team_id_cache[(args.owner, tn)] = tid # noqa: SLF001 + client._team_id_cache[(args.owner, tn)] = tid # noqa: SLF001 # internal write-through cache break if tid is not None: team_ids.append(tid) diff --git a/scripts/ops/check_migration_collisions.py b/scripts/ops/check_migration_collisions.py index 03c856608..302e980e7 100755 --- a/scripts/ops/check_migration_collisions.py +++ b/scripts/ops/check_migration_collisions.py @@ -91,6 +91,10 @@ def _gitea_get(path: str, params: dict[str, str] | None = None) -> bytes | None: req.add_header("Authorization", f"token {token}") req.add_header("Accept", "application/json") try: + # S310 (信任boundary): this function IS the outbound HTTP client for + # Gitea API calls. The call is intentional and controlled — we build + # the request ourselves and handle errors explicitly. Timeout=20s + # prevents indefinite hangs. with urllib.request.urlopen(req, timeout=20) as resp: # noqa: S310 return resp.read() except urllib.error.HTTPError as e: diff --git a/workspace-server/internal/handlers/templates.go b/workspace-server/internal/handlers/templates.go index efc239b5f..aced29222 100644 --- a/workspace-server/internal/handlers/templates.go +++ b/workspace-server/internal/handlers/templates.go @@ -243,10 +243,12 @@ func (h *TemplatesHandler) List(c *gin.Context) { log.Printf("templates list: skip %s: yaml.Unmarshal: %v", id, err) return } - runtime := strings.TrimSuffix(strings.TrimSpace(raw.Runtime), "-default") - if _, ok := knownRuntimes[runtime]; !ok { - log.Printf("templates list: skip %s: unsupported runtime %q", id, raw.Runtime) - return + if raw.Runtime != "" { + runtime := strings.TrimSuffix(strings.TrimSpace(raw.Runtime), "-default") + if _, ok := knownRuntimes[runtime]; !ok { + log.Printf("templates list: skip %s: unsupported runtime %q", id, raw.Runtime) + return + } } // Model comes from either top-level (legacy) or runtime_config.model (current). diff --git a/workspace-server/internal/handlers/templates_test.go b/workspace-server/internal/handlers/templates_test.go index f7d562667..0c9c55c8d 100644 --- a/workspace-server/internal/handlers/templates_test.go +++ b/workspace-server/internal/handlers/templates_test.go @@ -677,7 +677,7 @@ skills: [] t.Fatalf("parse: %v", err) } if len(resp) != 1 || resp[0].Model != "anthropic:claude-sonnet-4-6" { - t.Errorf("legacy top-level model not surfaced: %+v", resp) + t.Fatalf("legacy top-level model not surfaced: %+v", resp) } if resp[0].Runtime != "claude-code" { t.Errorf("Runtime should be claude-code for legacy template, got %q", resp[0].Runtime)