Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion .github/workflows/codeql-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ jobs:
break
fi
changed=""
sleep $((attempt * 3))
if [ "$attempt" -lt 3 ]; then
sleep $((attempt * 3))
fi
done
# GitHub caps /pulls/N/files at 3000 entries; a short list would hide
# source files behind a doc-only verdict, so require an exact count.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sast-semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
break
fi
changed=""
sleep $((attempt * 3))
if [ "$attempt" -lt 3 ]; then sleep $((attempt * 3)); fi
done
# GitHub caps /pulls/N/files at 3000 entries; a short list would hide
# source files behind a doc-only verdict, so require an exact count.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ jobs:
break
fi
changed=""
sleep $((attempt * 3))
if [ "$attempt" -lt 3 ]; then sleep $((attempt * 3)); fi
done
# GitHub caps /pulls/N/files at 3000 entries; a short list would hide
# source files behind a doc-only verdict, so require an exact count.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ jobs:
break
fi
changed=""
sleep $((attempt * 3))
if [ "$attempt" -lt 3 ]; then sleep $((attempt * 3)); fi
done
# GitHub caps /pulls/N/files at 3000 entries; a short list would hide
# source files behind a doc-only verdict, so require an exact count.
Expand Down
27 changes: 27 additions & 0 deletions docs/doctoring/required-workflow-path-filter-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,33 @@ repository: `changed-scope` (and `detect-languages` for CodeQL) succeed while
`scorecard` report `skipped`, and the **run conclusion** is `success`, not
`skipped`.

## Final-attempt backoff correction (2026-09-06, proposed)

The three current classifier copies in Security Scan, Semgrep, and Strix
slept after all three failed file-list requests, including nine seconds after
the final request when no retry remained. Keep three requests and the first
three- and six-second backoffs; omit only the final sleep. In the all-failed
path, requested sleep totals fall from 18 to 9 seconds (50%), not a measured
50% reduction in job runtime or organization queue occupancy. Success paths
and incomplete-list full scanning are unchanged.

The production-shell regression replaces only GitHub and sleep at the test
boundary. Across all three workflows, first/second/third success and complete
failure cover 12 cases. The RED commit `de49a612` produced 3 failures and
9 passes; all failures recorded an extra final sleep. No provider timeout,
trigger, permission, required context, or scanner policy is changed.

### CodeQL 동일 경로 보완 (2026-09-07, Proposed)

CodeQL의 `detect-languages` 안에도 같은 마지막 대기가 남아 있었다.
기존 셸 실행 테스트에 CodeQL을 추가한 `993cee1b`에서 1개 실패와
15개 성공을 확인했다. 실패 경로는 세 번 요청한 뒤 3·6·9초 대기를
기록했다. 마지막 대기만 제거하며, 세 번의 요청과 앞선 3·6초 대기,
조회 실패 시 `code=true`로 전체 검사하는 동작은 유지한다.
네 workflow의 성공 시점 세 가지와 전체 실패를 합쳐 16개 경로를 검증한다.
이는 해당 경로의 요청 대기를 18초에서 9초로 줄이는 수정이며,
조직 전체 적체나 실제 job 실행 시간이 50% 줄었다는 근거는 아니다.

## Safety boundary

This repair does not weaken any scanner's actual coverage. Every gate
Expand Down
50 changes: 50 additions & 0 deletions tests/test_docs_only_pr_runner_admission.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,56 @@ def test_strix_invalid_or_unreadable_admission_fails_the_metadata_job(
assert len(calls) == (1 if scenario == "api-error" else 0)


@pytest.mark.parametrize("filename", (*GATE_WORKFLOWS, "codeql-pr.yml"))
@pytest.mark.parametrize("success_attempt", (0, 1, 2, 3))
def test_classifier_sleeps_only_before_another_attempt(
tmp_path: Path, filename: str, success_attempt: int
) -> None:
"""Run the real classifier without network or waits; preserve retry coverage."""
calls = tmp_path / "calls"
outputs = tmp_path / "outputs"
prelude = '''
attempt_count=0
gh() {
printf 'request\\n' >> "$CALLS"
attempt_count=$(wc -l < "$CALLS")
if [ "$SUCCESS_ATTEMPT" -gt 0 ] && [ "$attempt_count" -eq "$SUCCESS_ATTEMPT" ]; then
printf 'docs/readme.md\\n'
else
return 1
fi
}
sleep() { printf '%s\\n' "$1" >> "$SLEEPS"; }
'''
sleeps = tmp_path / "sleeps"
job_name = "detect-languages" if filename == "codeql-pr.yml" else "changed-scope"
job = _top_level_job_block(_read(filename), job_name)
result = subprocess.run(
[shutil.which("bash") or "/bin/bash", "-c", prelude + _step_shell(job, "Classify changed paths")],
env={
"PATH": "/usr/bin:/bin",
"CALLS": str(calls),
"SLEEPS": str(sleeps),
"GITHUB_OUTPUT": str(outputs),
"SUCCESS_ATTEMPT": str(success_attempt),
"REPO": "owner/repo",
"PR": "17",
"EXPECTED_FILES": "1",
},
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, result.stderr
assert len(calls.read_text().splitlines()) == (success_attempt or 3)
assert (sleeps.read_text().splitlines() if sleeps.exists() else []) == ["3", "6"][: (success_attempt or 3) - 1]
expected = "false" if success_attempt else "true"
expected_outputs = {"code": expected}
if filename != "codeql-pr.yml":
expected_outputs["deps"] = expected
assert _read_outputs(outputs) == expected_outputs


def test_gate_classifier_shell_is_byte_identical_across_the_workflows():
"""The shared changed-path classifier shell must not drift."""
classifier_bodies = set()
Expand Down
Loading