Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
4e61b6a
fix(strix): treat ModelBehaviorError with zero findings as backend flake
seonghobae Aug 19, 2026
51c3815
fix(strix): qualify model behavior backend signal
seonghobae Aug 19, 2026
5512077
test(strix): require provider failures to stay non-passing
seonghobae Aug 20, 2026
85523e1
test(strix): enforce typed provider non-pass
seonghobae Aug 20, 2026
6e20dc1
fix(strix): fail closed on incomplete provider scans
seonghobae Aug 20, 2026
e6529aa
docs(strix): record typed provider non-pass
seonghobae Aug 20, 2026
a06e6e7
docs(changelog): record Strix fail-closed provider result
seonghobae Aug 20, 2026
86c262c
test(strix): bind typed provider error wording
seonghobae Aug 20, 2026
119d563
fix(strix): keep PR scoped to provider scan contract
seonghobae Aug 20, 2026
c6bcef0
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 20, 2026
b3ce110
fix(strix): authenticate GitHub Models HTTP 410 fallback
seonghobae Aug 20, 2026
e21951d
fix(strix): classify Caido bootstrap outages
seonghobae Aug 20, 2026
2e8e782
fix(strix): isolate scanner output from scan target
seonghobae Aug 20, 2026
562c923
Merge branch 'main' into codex/pr1138-current-main-successor
seonghobae Aug 20, 2026
2d2037c
docs: complete coordinator docstring coverage
seonghobae Aug 20, 2026
a0fe050
test: complete workflow contract docstrings
seonghobae Aug 20, 2026
956ae2b
fix: keep baseline-only Strix outages non-blocking (#1169)
seonghobae Aug 20, 2026
9a4d1e1
fix(strix): fail closed after provider exhaustion
seonghobae Aug 20, 2026
9ddda5b
Merge branch 'main' into codex/pr1138-current-main-successor
opencode-agent[bot] Aug 20, 2026
a82b801
fix(ci): redact untrusted github method diagnostics
seonghobae Aug 20, 2026
945d5d5
fix(strix): include backend app dependency context
seonghobae Aug 21, 2026
ab0f179
fix(strix): include contextual orchestrator sibling context
seonghobae Aug 21, 2026
035343c
fix(strix): include Rust workspace context
seonghobae Aug 21, 2026
2cd1d34
fix(strix): classify ModelBehaviorError as retryable protocol evidence
seonghobae Aug 21, 2026
d8d0546
fix(strix): require module-qualified ModelBehaviorError context
seonghobae Aug 21, 2026
affc130
fix(strix): retry report-only provider failures
Aug 21, 2026
782d0fc
docs(strix): record report-log fallback boundary
Aug 21, 2026
4889b19
Merge remote-tracking branch 'origin/main' into HEAD
Aug 21, 2026
56588e1
fix(strix): preserve sanitized report artifacts
Aug 21, 2026
48e5b61
fix(strix): preserve behavior error case sensitivity
seonghobae Aug 21, 2026
8d69548
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 21, 2026
f46eb9a
Merge branch 'main' into fix/strix-github-models-retirement-fallback
seonghobae Aug 21, 2026
8541905
Merge PR #1195 ModelBehaviorError contract into Strix owner lane
seonghobae Aug 21, 2026
144df9f
docs(strix): remove duplicate ModelBehaviorError doctoring record
seonghobae Aug 21, 2026
52a62a0
fix(security): raise pip-audit runner pip floor
seonghobae Aug 21, 2026
6087b52
chore(strix): restore causal owner scope
seonghobae Aug 21, 2026
ebda81f
Merge branch 'main' into codex/pr1138-current-main-successor
opencode-agent[bot] Aug 21, 2026
3b279ef
fix(security): raise pip audit lock past PYSEC-2026-3721
seonghobae Aug 21, 2026
9002f4d
Merge remote-tracking branch 'origin/main' into fix/pip-audit-2026-3721
seonghobae Aug 21, 2026
c9a0592
fix(strix): converge protected-main scheduler contracts
seonghobae Aug 21, 2026
04a0b98
fix(ci): refresh pip audit lock
seonghobae Aug 21, 2026
a88f017
chore(strix): restore pip lock to security owner
seonghobae Aug 21, 2026
1f8878d
Merge branch 'main' into codex/pr1138-current-main-successor
seonghobae Aug 21, 2026
c1bac0a
ci: refresh pip audit runtime
seonghobae Aug 21, 2026
0bf6874
chore(strix): restore canonical pip lock ownership
seonghobae Aug 21, 2026
4de4929
ci: refresh dependency and scheduler contracts
seonghobae Aug 21, 2026
ab1b5de
Merge current main and refresh scheduler contracts
seonghobae Aug 21, 2026
8db8aa8
Merge branch 'main' into codex/pr1138-current-main-successor
opencode-agent[bot] Aug 21, 2026
09c3439
merge: synchronize Strix fail-closed repair with protected main
seonghobae Aug 23, 2026
412370e
test(strix): execute complete fail-closed branch
seonghobae Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/strix-changed-path-quality-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ on:
branches: [main]
paths:
- ".github/workflows/strix-changed-path-quality-ci.yml"
- ".github/workflows/strix.yml"
- "CHANGELOG.md"
- "docs/doctoring/strix-legal-git-paths.md"
- "docs/doctoring/strix-model-behavior-error.md"
- "docs/doctoring/strix-quality-timeout-fixtures.md"
- "scripts/ci/strix_quick_gate.sh"
- "scripts/ci/test_strix_quick_gate.sh"
- "tests/test_strix_changed_path_policy.py"
- "tests/test_strix_model_behavior_error.py"
- "tests/test_strix_nvidia_nim_not_found_fallback.py"
- "tests/test_strix_workflow_dependency_hashes.py"
- "tests/test_strix_quality_timeout_fixture_budget.py"

Expand Down Expand Up @@ -66,6 +70,6 @@ jobs:
test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}"
python -m coverage run -m pytest tests -q
bash scripts/ci/test_strix_quick_gate.sh
python -m compileall -q tests/test_strix_changed_path_policy.py tests/test_strix_workflow_dependency_hashes.py tests/test_strix_quality_timeout_fixture_budget.py
python -m compileall -q tests/test_strix_changed_path_policy.py tests/test_strix_model_behavior_error.py tests/test_strix_nvidia_nim_not_found_fallback.py tests/test_strix_workflow_dependency_hashes.py tests/test_strix_quality_timeout_fixture_budget.py
bash -n scripts/ci/strix_quick_gate.sh
git diff --exit-code
40 changes: 18 additions & 22 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -853,10 +853,11 @@ jobs:
# Capture the gate exit code plus its console output. The gate returns
# exit 1 both for genuine blocking vulnerabilities AND for
# LLM-backend-unavailable outcomes (GitHub Models "Too many requests"
# rate limits, OpenAI quota starvation, 413 tokens_limit_reached
# token-cap, connection/warm-up failures) that could not complete a scan. A backend outage is CI
# infrastructure noise, not a security finding, so it must not fail
# the required check and block merges.
# rate limits, OpenAI quota starvation, 413 tokens_limit_reached,
# connection/warm-up failures, and scanner ModelBehaviorError) that
# could not complete a scan. Provider failure is typed infrastructure
# evidence, but remains non-passing because no authoritative complete
# vulnerability result exists.
strix_run_log="$RUNNER_TEMP/strix_gate_console.log"
strix_rc=0
set +e
Expand All @@ -876,38 +877,33 @@ jobs:
fi

# Recognized signals that the LLM backend was unavailable / starved.
backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|Error code:[[:space:]]*410|github_models_retirement_brownout|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404'
backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|emitted provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404|Error during penetration test: loginAsGuest failed after [0-9]+ attempts: curl exit 7: curl: \(7\) Failed to connect to 127\.0\.0\.1 port 48080'
model_behavior_error_signal='(^|[^A-Za-z0-9_])(agents|pydantic_ai|strix)(\.[A-Za-z_][A-Za-z0-9_]*)*\.ModelBehaviorError([^A-Za-z0-9_]|$)'
# Any evidence that a vulnerability was actually reported. Its presence
# forces a hard failure so real findings are NEVER downgraded. Keep the
# severity branch anchored away from identifiers so environment lines
# such as STRIX_FAIL_ON_MIN_SEVERITY do not look like findings.
reported_vulnerability_signal='Vulnerabilities[[:space:]]+[1-9]|(^|[^A-Za-z0-9_])severity[[:space:]]*:'

# The gate may already have exempted an earlier, out-of-scope
# finding (unchanged-file evidence, or below the configured minimum
# severity) and logged "allowing pipeline continuation" before
# moving on to a later, independent model attempt. That earlier
# finding's own "Vulnerabilities N" / "severity:" text must not
# poison the backend-unavailable check for a later, unrelated
# provider outage. Scope the neutral-skip decision to the log tail
# after the LAST such continuation marker (the full log when no
# exemption occurred), so an unresolved vulnerability anywhere in
# that scope still fails closed.
# An earlier out-of-scope/below-threshold finding may already have
# been exempted by the trusted gate. Classify a later provider
# outage from the tail after the last continuation marker, but keep
# that incomplete later scan non-passing.
strix_neutralization_scope_log="$strix_run_log"
if grep -Fq 'allowing pipeline continuation' "$strix_run_log"; then
strix_neutralization_scope_log="$RUNNER_TEMP/strix_gate_console_tail.log"
awk '/allowing pipeline continuation/{buf=""; next} {buf=buf $0 "\n"} END{printf "%s", buf}' \
"$strix_run_log" > "$strix_neutralization_scope_log"
fi

# Neutral skip only when ALL hold: a backend-unavailability signal is
# present and no vulnerability was reported in the relevant scope.
# This preserves real security gating while keeping uncontrollable
# provider outages from blocking current-head merge progress.
if grep -Eiq "$backend_unavailable_signal" "$strix_neutralization_scope_log" \
# Classify provider/backend exhaustion only when no vulnerability
# finding was emitted. Classification improves diagnosis; it never
# converts an incomplete scan into passing security evidence.
if ( grep -Eiq "$backend_unavailable_signal" "$strix_neutralization_scope_log" \
|| grep -Eq "$model_behavior_error_signal" "$strix_neutralization_scope_log" ) \
&& ! grep -Eiq "$reported_vulnerability_signal" "$strix_neutralization_scope_log"; then
echo "::warning title=Strix backend unavailable::Strix could not complete because its LLM backend was unavailable (rate limit / token cap / connection or warm-up failure) before producing a vulnerability report. Treating as a neutral skip so an infrastructure outage does not block merges; genuine findings still fail the check. See the strix-reports artifact and the run log."
exit 0
echo "::error title=STRIX_PROVIDER_UNAVAILABLE::Strix could not complete authoritative vulnerability analysis because its provider/backend was unavailable (rate limit, token cap, connection, warm-up, or model-behavior failure). See the strix-reports artifact and run log."
exit "$strix_rc"
fi

echo "Strix reported security findings or failed for a non-backend reason; failing the required check (gate exit ${strix_rc})." >&2
Comment thread
seonghobae marked this conversation as resolved.
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Semantic Versioning where the repository publishes a release.

### Added

- Classify Strix `ModelBehaviorError` and provider exhaustion as typed
`STRIX_PROVIDER_UNAVAILABLE` evidence while preserving a nonzero required
check. Incomplete scans and reported vulnerabilities both fail closed.

- Added an hourly organization commercial-readiness coordinator that discovers writable repositories, honors enabled dedicated writer leases and fully paginated live writer runs, refetches exact repository/workflow/run/PR state before dispatch, rotates bounded review-repair and opt-in NVIDIA OpenCode product-development targets, fails nonzero on fleet-wide inspection or dispatch outages, retains three-day JSON receipts, and keeps the existing 15-minute merge scheduler authoritative.
- Added a dedicated Quarantine Sandbox Runtime hourly caller at minute 14 that targets protected `develop`, dispatches at most one exact-head repair, applies a two-hour same-head retry floor, preserves non-cancelling single-flight execution, and maps only the established scheduler credentials with job-scoped OIDC.
- Added a dedicated OriginWeave hourly caller that invokes the product-neutral central scheduler with the exact repository, protected `main` branch, one-dispatch budget, two-hour same-head retry floor, non-cancelling single-flight heartbeat, job-scoped OIDC, and only the established scheduler credentials.
Expand Down Expand Up @@ -51,6 +55,36 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Publish only the sanitized cumulative Strix report tree, avoiding a later
copy of relative scanner output that could reintroduce known internal warning
text into uploaded security evidence.

- Retry configured Strix fallback models when the primary provider records a
rate-limit or infrastructure failure only in its structured report log, and
evaluate each fallback against its newest report without letting an older
failed attempt poison a complete later report.

- Include the exact `backend/app/*.py` package context in PR-scoped Strix
scans when a module in that package changes. The trusted resolver uses a
NUL-delimited exact-head tree listing, copies unchanged dependencies from
the trusted base, and keeps changed-file attribution and provider failures
fail-closed.
- Include the exact `contextual_orchestrator/*.py` sibling-import context under
the same NUL-delimited exact-head and fail-closed path boundary without
expanding changed-file finding attribution.
- Treat Rust source and Cargo manifests as governed Strix inputs and include
trusted Cargo, toolchain, and `deny.toml` context when a workflow change
scopes a Rust workspace.
- Run Strix with an explicit canonical scan target from a temporary working
directory outside that target, so scanner state and relative reports cannot
become self-scanned source findings; preserve those reports as gate evidence.
PR-scoped Python scans also include the PostgreSQL introspection security
helpers when that package exists in the target repository. PR scopes now live
below the gate's private runtime directory so unrelated temporary-file
cleanup cannot remove scan input during PR-head materialization.
- Classify Strix `ModelBehaviorError` with zero reported vulnerabilities as
retryable model-protocol evidence, while keeping `Vulnerabilities [1-9]` and
other severity signals fail-closed.
- Derived `org-queue-sweep`'s rotation index (added in `ContextualWisdomLab/.github#1220` to stop the walk-order starvation from `ContextualWisdomLab/.github#1219`) from a persistent `ORG_SWEEP_ROTATION_COUNTER` repository variable incremented by exactly one at the start of every actual sweep execution, instead of `github.run_number` (which increments on every trigger of this workflow, not only the sweep schedule — Devin review finding on `#1220`) or a wall-clock tick alone (which can repeat an offset when this single-flight, up-to-60-minute job runs behind schedule by an exact multiple of the repository count — CodeRabbit review finding on `#1223`). Falls back to the wall-clock tick only if the persistent counter itself is unavailable, so a fairness mechanism never blocks the sweep's review-dispatch/merge work.
- Retried the Strix scan up to `STRIX_TRANSIENT_RETRY_PER_MODEL` times, same model, when the log shows the upstream strix-agent Caido sandbox bootstrap timing race (`loginAsGuest failed after N attempts` / `Failed to connect to 127.0.0.1 port <port>`; tracked upstream as usestrix/strix#1036, #1037, #1056). A slow CI runner can exceed strix-agent's fixed 10-attempt sandbox-login budget before its local intercepting proxy is reachable, even though the penetration test itself never started and no vulnerability evidence was produced or lost; the Docker image is already cached from the failed attempt, so a same-model retry is cheap and typically clears the one-off boot race. Not wired into cross-model fallback, since switching LLM models cannot change local sandbox container boot timing.
- Replaced nonexistent `job.workflow_repository` / `job.workflow_sha` / `job.workflow_ref` / `job.workflow_file_path` context references (actionlint: "property ... is not defined in object type") in `pr-review-fix-scheduler.yml`'s called-workflow source verification and `exact-artifact-sbom-attestation.yml`'s trusted-verifier checkout. Both always failed closed on the missing properties (ContextualWisdomLab/.github#1212) or, for the SBOM attestation checkout, silently resolved an empty repository/ref instead of the pinned trusted source (downstream `gh attestation verify --signer-repo`/`--signer-workflow`, using the separately hardcoded `SIGNER_REPOSITORY` constant rather than any workflow_ref, still failed closed on the resulting empty signer identity). `github.workflow_ref`/`github.workflow_sha` are real, documented properties, but for a `workflow_call` target they reflect the top-level *calling* workflow, not the reusable workflow's own file — a prefix match against the reusable workflow's own path can never succeed. `exact-artifact-sbom-attestation.yml`'s checkout now uses `github.workflow_sha` (correct today: it has no callers yet); `pr-review-fix-scheduler.yml`'s identity check instead validates `github.repository`, since every current caller uses a local, same-repo `uses: ./...` where caller and callee share one commit and `github.workflow_sha` is still the right pin. Tracked follow-up for the SBOM attestation checkout once a real (potentially cross-repo) caller exists: ContextualWisdomLab/.github#1228.
Expand Down
53 changes: 53 additions & 0 deletions docs/doctoring/strix-model-behavior-error.md
Comment thread
seonghobae marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Strix ModelBehaviorError classifier

기준일: **2026-08-21**

## Incident

Required Strix scans can fail closed after the agent runtime raises
`ModelBehaviorError` even when the log reports `Vulnerabilities 0`. The
exception means the selected model did not follow Strix's tool-calling
protocol. Treating that protocol failure as a security finding blocked
current-head progress on otherwise empty scans.

## Decision

`scripts/ci/strix_quick_gate.sh` recognizes a **module-qualified**
`ModelBehaviorError` from `agents`, `pydantic_ai`, or `strix` as retryable
model evidence. A bare source-file mention is not enough. The gate moves to
the configured fallback sequence and does not retry the same model. The outer
`.github/workflows/strix.yml` classifies the failure as typed provider evidence
only when that signal is present **and** the log contains no vulnerability
evidence, while preserving the nonzero result because the scan is incomplete.

`Vulnerabilities[[:space:]]+[1-9]` and `severity:` markers remain blocking.
Generic warnings, timeouts, provider failures, and MEDIUM-or-higher findings
are unchanged.

## Verification contract

`tests/test_strix_model_behavior_error.py` executes the production classifier
and the outer workflow neutralization condition against bounded synthetic
logs. It proves:

1. a module-qualified `agents`/`pydantic_ai`/`strix` `ModelBehaviorError`
plus `Vulnerabilities 0` is retryable and typed non-passing;
2. the same exception plus `Vulnerabilities 1` stays fail-closed;
3. lowercase application prose or a bare `ModelBehaviorError` token is not
classified as the runtime exception;
4. the identifier is wired into infrastructure detection and cross-model
fallback, never same-model retry.

## Rollback

If a future Strix release renames the exception, add the exact new identifier
and a matching regression. Do not remove the vulnerability fail-closed guard.

## References (APA 7th)

GitHub. (n.d.). *Workflow syntax for GitHub Actions*. GitHub Docs. Retrieved
August 21, 2026, from
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax

GitHub. (n.d.). *Using workflow run logs*. GitHub Docs. Retrieved August 21,
2026, from https://docs.github.com/en/actions/how-tos/monitor-workflows/use-workflow-run-logs
16 changes: 10 additions & 6 deletions docs/doctoring/strix-nvidia-nim-not-found-fallback.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ combining with an unrelated application `404` to spoof infrastructure fallback.
Provider-side failure also remains a fail-closed incomplete scan until a distinct
fallback produces complete evidence.

The outer workflow may classify exhausted provider infrastructure as neutral only
when the run log contains no vulnerability signal. Any reported severity or
non-zero vulnerability count remains blocking. Scanner reports and attempt logs
remain available as artifacts.
Exhausted provider infrastructure remains fail-closed even when the trusted
gate has classified every observed threshold finding as outside the pull
request's changed files. That classification scopes authoritative findings; it
cannot prove that an incomplete provider-exhausted scan observed every finding.
Changed, unmapped, and changed-manifest findings also remain blocking. Scanner
reports and attempt logs remain available as artifacts.

## Verification contract

Expand All @@ -48,8 +50,10 @@ Regression evidence proves that:
5. model-catalog 404s enter cross-model fallback but never same-model retry;
6. the primary and first fallback are current NVIDIA hosted models;
7. GitHub Models remain later cross-provider fallbacks;
8. vulnerability signals prevent neutral infrastructure classification; and
9. the required-workflow smoke contract pins these properties.
8. provider exhaustion remains non-passing after unchanged baseline findings;
9. changed, unmapped, and changed-manifest findings also block after provider
exhaustion; and
10. the required-workflow smoke contract pins these properties.

## Limitations

Expand Down
Loading
Loading