github-actions[bot] commented on code in PR #66119:
URL: https://github.com/apache/doris/pull/66119#discussion_r3679918127
##########
.github/workflows/code-review-runner.yml:
##########
@@ -708,3 +1303,84 @@ jobs:
fi
echo "Codex automated review failed: ${error_msg}"
exit 1
+
+ publish-terminal-status:
+ needs: code-review
+ if: ${{ always() }}
+ runs-on: ubuntu-latest
+ permissions:
+ statuses: write
+ steps:
+ - name: Publish terminal Code Review status
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ HEAD_SHA: ${{ inputs.head_sha }}
+ REVIEW_CONTEXT_OUTCOME: ${{
needs.code-review.outputs.review_context_outcome }}
+ REVIEW_OUTCOME: ${{ needs.code-review.outputs.review_outcome }}
+ CONFIGURED_DEFERRED: ${{
needs.code-review.outputs.configured_deferred }}
+ REVIEW_DEFERRED: ${{ needs.code-review.outputs.review_deferred }}
+ RUN_URL: ${{ github.server_url }}/${{ github.repository
}}/actions/runs/${{ github.run_id }}
+ run: |
+ state='error'
+ description="Automated review failed for ${HEAD_SHA}."
+ if [ "$CONFIGURED_DEFERRED" = 'true' ] || [ "$REVIEW_DEFERRED" =
'true' ]; then
+ state='pending'
+ description="Automated review is durably queued for a later retry."
+ elif [ "$REVIEW_CONTEXT_OUTCOME" = 'success' ] && [
"$REVIEW_OUTCOME" = 'success' ]; then
+ state='success'
+ description="Automated review completed for ${HEAD_SHA}."
+ fi
+ gh api "repos/${REPO}/statuses/${HEAD_SHA}" \
+ -X POST \
+ -f state="$state" \
+ -f context='code-review' \
+ -f description="$description" \
+ -f target_url="$RUN_URL"
+
+ acknowledge-terminal-request:
+ needs:
+ - code-review
+ - publish-terminal-status
+ if: ${{ always() }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install ossutil
+ run: |
+ tmp_dir="$(mktemp -d)"
+ trap 'rm -rf "$tmp_dir"' EXIT
+ curl -fsSL -o "$tmp_dir/ossutil.zip"
https://gosspublic.alicdn.com/ossutil/1.7.19/ossutil-v1.7.19-linux-amd64.zip
+ unzip -q "$tmp_dir/ossutil.zip" -d "$tmp_dir"
+ sudo install -d -o root -g root -m 0755 /opt/doris-ci
+ sudo install -o root -g root -m 0755
"$tmp_dir/ossutil-v1.7.19-linux-amd64/ossutil" /opt/doris-ci/ossutil
+
+ - name: Acknowledge persisted request
+ env:
+ OSS_AK: ${{ secrets.OSS_AK }}
+ OSS_SK: ${{ secrets.OSS_SK }}
+ OSS_ENDPOINT: oss-cn-hongkong.aliyuncs.com
+ REQUEST_OBJECT: ${{ needs.code-review.outputs.request_object }}
+ SUCCESSOR_REQUEST_OBJECT: ${{
needs.code-review.outputs.successor_request_object }}
+ PRESERVE_REQUEST: ${{ needs.code-review.outputs.preserve_request }}
+ CONFIGURED_DEFERRED: ${{
needs.code-review.outputs.configured_deferred }}
+ REVIEW_DEFERRED: ${{ needs.code-review.outputs.review_deferred }}
+ STATUS_RESULT: ${{ needs.publish-terminal-status.result }}
+ run: |
+ if [ -z "$REQUEST_OBJECT" ]; then
+ exit 0
+ fi
+ if [ "$STATUS_RESULT" != 'success' ]; then
+ echo 'Terminal status was not persisted; retaining the review
request.'
+ exit 0
+ fi
+ if [ "$CONFIGURED_DEFERRED" = 'true' ] || [ "$REVIEW_DEFERRED" =
'true' ]; then
+ if [ -z "$SUCCESSOR_REQUEST_OBJECT" ] \
+ || ! /opt/doris-ci/ossutil -i "$OSS_AK" -k "$OSS_SK" -e
"$OSS_ENDPOINT" stat "$SUCCESSOR_REQUEST_OBJECT" >/dev/null; then
+ echo 'Deferred successor is not durable; retaining the review
request.' >&2
+ exit 1
+ fi
+ elif [ "$PRESERVE_REQUEST" = 'true' ]; then
+ echo 'Deferred request persistence was interrupted; retaining the
review request.'
+ exit 0
+ fi
+ /opt/doris-ci/ossutil -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT"
rm -f "$REQUEST_OBJECT"
Review Comment:
[P1] Do not delete an in-place deferred successor
For a direct `/review`, `REQUEST_OBJECT` is already
`review-pending/<pr>-<head>-<request>.json`, and both `defer_review` helpers
write the successor back to that same key. This branch successfully stats that
key and then the final `rm` deletes it as the "old" request. Whenever the
direct run has to defer for quota/rate/auth availability, the status stays
pending but no pending object remains for the scheduler. Treat an equal
request/successor key as an in-place transition and retain it (with an
integration test for direct-request deferral).
<!-- doris-codex-review:30511911292:1 -->
##########
.github/workflows/code-review-runner.yml:
##########
@@ -708,3 +1303,84 @@ jobs:
fi
echo "Codex automated review failed: ${error_msg}"
exit 1
+
+ publish-terminal-status:
+ needs: code-review
+ if: ${{ always() }}
+ runs-on: ubuntu-latest
+ permissions:
+ statuses: write
+ steps:
+ - name: Publish terminal Code Review status
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ HEAD_SHA: ${{ inputs.head_sha }}
+ REVIEW_CONTEXT_OUTCOME: ${{
needs.code-review.outputs.review_context_outcome }}
+ REVIEW_OUTCOME: ${{ needs.code-review.outputs.review_outcome }}
+ CONFIGURED_DEFERRED: ${{
needs.code-review.outputs.configured_deferred }}
+ REVIEW_DEFERRED: ${{ needs.code-review.outputs.review_deferred }}
+ RUN_URL: ${{ github.server_url }}/${{ github.repository
}}/actions/runs/${{ github.run_id }}
+ run: |
+ state='error'
+ description="Automated review failed for ${HEAD_SHA}."
+ if [ "$CONFIGURED_DEFERRED" = 'true' ] || [ "$REVIEW_DEFERRED" =
'true' ]; then
+ state='pending'
+ description="Automated review is durably queued for a later retry."
+ elif [ "$REVIEW_CONTEXT_OUTCOME" = 'success' ] && [
"$REVIEW_OUTCOME" = 'success' ]; then
+ state='success'
+ description="Automated review completed for ${HEAD_SHA}."
+ fi
+ gh api "repos/${REPO}/statuses/${HEAD_SHA}" \
Review Comment:
[P1] Keep the status owned by the newest review request
Two `/review` commands can target the same unchanged head. The newer caller
posts `code-review=pending` and then waits behind this workflow's global
concurrency group, but when the older runner reaches this unconditional status
write it replaces that pending state with `success`. The newer direct runner
does not publish pending again when it eventually starts, so the required check
can remain green for the whole newer review. Bind the aggregate status to a
persisted per-head request generation (or otherwise compare ownership before
writing), and test the A-running/B-enqueued/A-finishes ordering.
<!-- doris-codex-review:30511911292:1 -->
##########
.github/workflows/code-review-runner.yml:
##########
@@ -488,61 +707,460 @@ jobs:
HEAD_SHA: ${{ inputs.head_sha }}
BASE_SHA: ${{ inputs.base_sha }}
+ - name: Grant isolated Codex user access to review context
+ run: |
+ sudo chown -R "$CODEX_REVIEW_USER:$CODEX_REVIEW_USER"
"$REVIEW_CONTEXT_DIR"
+
- name: Run automated code review
id: review
timeout-minutes: 115
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OSS_AK: ${{ secrets.OSS_AK }}
+ OSS_SK: ${{ secrets.OSS_SK }}
+ OSS_ENDPOINT: oss-cn-hongkong.aliyuncs.com
+ OSS_CODEX_AUTH_STATUS_OBJECT:
oss://doris-community-ci/codex/auth-status.json
+ OSS_CODEX_REVIEW_PENDING_PREFIX:
oss://doris-community-ci/codex/review-pending
+ OSS_CODEX_REVIEW_CLAIMED_PREFIX:
oss://doris-community-ci/codex/review-claimed
+ AUTH_UNAVAILABLE: ${{ steps.configure_auth.outputs.auth_unavailable
}}
+ CONFIGURED_ALL_QUOTA_EXHAUSTED: ${{
steps.configure_auth.outputs.all_quota_exhausted }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ inputs.pr_number }}
HEAD_SHA: ${{ inputs.head_sha }}
+ BASE_SHA: ${{ inputs.base_sha }}
+ REVIEW_FOCUS: ${{ inputs.review_focus }}
+ DEFERRED_REQUEST_ID: ${{ inputs.deferred_request_id }}
+ DEFERRED_CLAIM_ID: ${{ inputs.deferred_claim_id }}
run: |
- GOAL_PROMPT="$(cat "$REVIEW_CONTEXT_DIR/codex_goal_prompt.txt")"
- review_started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ write_review_output() {
+ local key="$1"
+ local value="$2"
+ {
+ echo "${key}<<EOF"
+ printf '%s\n' "$value"
+ echo "EOF"
+ } >> "$GITHUB_OUTPUT"
+ }
+
+ OSSUTIL=/opt/doris-ci/ossutil
+
+ defer_review() {
+ local retry_at="$1" request_id request_object
+ request_id="${DEFERRED_REQUEST_ID:-$GITHUB_RUN_ID}"
+
request_object="$OSS_CODEX_REVIEW_PENDING_PREFIX/${PR_NUMBER}-${HEAD_SHA}-${request_id}.json"
+ local request_file
+ request_file="$(mktemp "$RUNNER_TEMP/codex-review-pending.XXXXXX")"
+ jq -n \
+ --arg pr_number "$PR_NUMBER" \
+ --arg head_sha "$HEAD_SHA" \
+ --arg base_sha "$BASE_SHA" \
+ --arg review_focus "$REVIEW_FOCUS" \
+ --arg not_before "$retry_at" \
+ --arg request_id "$request_id" \
+ --arg lease_id '' \
+ --arg lease_expires_at '' \
+ '{pr_number: $pr_number, head_sha: $head_sha, base_sha:
$base_sha,
+ review_focus: $review_focus, not_before: $not_before,
request_id: $request_id,
+ lease_id: $lease_id, lease_expires_at: $lease_expires_at}' >
"$request_file"
+ write_review_output "preserve_request" "true"
+ "$OSSUTIL" -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f \
+ "$request_file" "$request_object"
+ rm -f "$request_file"
+ write_review_output "deferred_review" "true"
+ write_review_output "successor_request_object" "$request_object"
+ }
+
+ auth_object_args=()
+ known_auth_object_args=()
+ while IFS= read -r auth_object; do
+ auth_object_args+=(--auth-object "$auth_object")
+ known_auth_object_args+=(--known-auth-object "$auth_object")
+ done < "$CODEX_AUTH_OBJECTS_FILE"
+ if [ "${#auth_object_args[@]}" -eq 0 ]; then
+ write_review_output "all_quota_exhausted" "false"
+ write_review_output "failure_reason" "No valid Codex auth account
is available for review."
+ exit 1
+ fi
- set +e
- # GitHub-hosted runners are ephemeral. Avoid workspace-write here
because
- # Codex uses bubblewrap for that mode and uid maps can be
unavailable.
- codex exec --goal "$GOAL_PROMPT" \
- --cd "$GITHUB_WORKSPACE" \
- --model "gpt-5.6-sol" \
- --config "model_reasoning_effort=xhigh" \
- --sandbox danger-full-access \
- --color never \
- --json \
- --output-last-message
"$REVIEW_CONTEXT_DIR/codex-final-message.txt" \
- > "$REVIEW_CONTEXT_DIR/codex-events.jsonl" \
- 2> >(tee "$REVIEW_CONTEXT_DIR/codex-stderr.log" >&2)
- status=$?
- set -e
+ sync_auth_state() {
+ "$OSSUTIL" -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f \
+ "$CODEX_AUTH_STATE_FILE" "$OSS_CODEX_AUTH_STATUS_OBJECT"
+ }
+
+ record_auth_result() {
+ local result="$1"
+ local http_status="$2"
+ local retry_after="$3"
+ local record_args=(
+ record
+ --state "$CODEX_AUTH_STATE_FILE"
+ --auth-object "$CODEX_AUTH_STATE_KEY"
+ "${known_auth_object_args[@]}"
+ --result "$result"
+ )
+ if [ -n "$http_status" ]; then
+ record_args+=(--http-status "$http_status")
+ fi
+ if [ -n "$retry_after" ]; then
+ record_args+=(--retry-after "$retry_after")
+ fi
+ "$CODEX_AUTH_STATE_HELPER" "${record_args[@]}"
+ }
+
+ install_selected_auth() {
+ local candidate_auth_file
+ candidate_auth_file="$(mktemp "$RUNNER_TEMP/codex-auth.XXXXXX")"
+ "$OSSUTIL" -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f \
+ "$CODEX_AUTH_OSS_OBJECT" "$candidate_auth_file"
+ "$CODEX_AUTH_STATE_HELPER" validate --auth "$candidate_auth_file"
+ sudo install -o "$CODEX_REVIEW_USER" -g "$CODEX_REVIEW_USER" -m
600 \
+ "$candidate_auth_file" "$CODEX_HOME/auth.json"
+ install -m 600 "$candidate_auth_file" "$CODEX_AUTH_SNAPSHOT"
+ rm -f "$candidate_auth_file"
+ }
+
+ sync_refreshed_auth() {
+ local auth_dir refreshed_auth_file promoted_auth_file
refreshed_digest cmp_status
+ if ! auth_dir="$(mktemp -d
"$RUNNER_TEMP/codex-refreshed-auth.XXXXXX")"; then
+ return 1
+ fi
+ refreshed_auth_file="$auth_dir/refreshed-auth.json"
+ promoted_auth_file="$auth_dir/promoted-auth.json"
+ if ! sudo -u "$CODEX_REVIEW_USER" cat "$CODEX_HOME/auth.json" >
"$refreshed_auth_file"; then
+ rm -rf "$auth_dir"
+ return 1
+ fi
+ if ! "$CODEX_AUTH_STATE_HELPER" promote \
+ --baseline "$CODEX_AUTH_SNAPSHOT" \
+ --candidate "$refreshed_auth_file" \
+ --output "$promoted_auth_file"; then
+ rm -rf "$auth_dir"
+ return 1
+ fi
+ if [ ! -s "$promoted_auth_file" ]; then
+ rm -rf "$auth_dir"
+ return 1
+ fi
+ cmp -s "$promoted_auth_file" "$CODEX_AUTH_SNAPSHOT"
+ cmp_status=$?
+ if [ "$cmp_status" -gt 1 ]; then
+ rm -rf "$auth_dir"
+ return 1
+ fi
+ if [ "$cmp_status" -eq 1 ]; then
+ if ! "$OSSUTIL" -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp
-f \
Review Comment:
[P1] Keep OSS secrets out of post-agent argv
The sanitized child environment and absolute binary path do not close the
secret boundary. A prompt-injected Codex process can leave a detached low-UID
process polling the shared process table; after `codex exec` returns, this
privileged invocation puts both repository-wide OSS keys in `ossutil`'s argv,
where that process can read and exfiltrate them. This is distinct from
inheriting the keys or replacing `ossutil`. Run the secret-bearing work after
the entire agent process tree is gone (ideally in a fresh job/container) and
pass credentials through a root-only channel that never exposes them in argv.
<!-- doris-codex-review:30511911292:1 -->
##########
.github/scripts/codex_auth_state.py:
##########
@@ -0,0 +1,582 @@
+#!/usr/bin/env python3
+"""Persist and select the rotating credentials used by code review jobs."""
+
+from __future__ import annotations
+
+import argparse
+import base64
+import copy
+import json
+import re
+import tempfile
+from dataclasses import dataclass
+from datetime import datetime, timedelta, timezone
+from pathlib import Path
+from typing import Any
+
+
+STATE_VERSION = 1
+AVAILABLE = "available"
+QUOTA_EXHAUSTED = "quota_exhausted"
+RATE_LIMITED = "rate_limited"
+AUTHENTICATION_FAILED = "authentication_failed"
+TRANSIENT_FAILURE = "transient_failure"
+STATUS_VALUES = {AVAILABLE, QUOTA_EXHAUSTED, RATE_LIMITED,
AUTHENTICATION_FAILED}
+QUOTA_RETRY_DELAY = timedelta(hours=12)
+RATE_LIMIT_RETRY_DELAY = timedelta(hours=1)
+PERMANENT_AUTH_FAILURE_PATTERN = re.compile(
+ r"refresh[\s_-]*token.*(?:revoked|expired|already\s+used)|"
+ r"(?:revoked|expired).*refresh[\s_-]*token",
+ re.IGNORECASE,
+)
+USAGE_LIMIT_MESSAGE_PATTERN = re.compile(
+ r"you(?:'|\u2019)ve hit your usage limit|usage limit",
+ re.IGNORECASE,
+)
+USAGE_LIMIT_RETRY_PATTERN = re.compile(
+ r"try again
at\s+(?:(?P<date>[A-Za-z]{3,9}\s+\d{1,2}(?:st|nd|rd|th)?,\s+\d{4}\s+"
+ r"\d{1,2}:\d{2}\s+(?:AM|PM))|(?P<time>\d{1,2}:\d{2}\s+(?:AM|PM)))",
+ re.IGNORECASE | re.DOTALL,
+)
+CONTENT_DIGEST_PATTERN = re.compile(r"[0-9a-f]{64}")
+
+
+@dataclass(frozen=True)
+class Selection:
+ auth_object: str | None
+ all_quota_exhausted: bool
+ next_retry_at: str | None
+
+
+@dataclass(frozen=True)
+class FailureClassification:
+ kind: str
+ http_status: int | None
+ retry_after: str | None
+
+
+def utc_now() -> datetime:
+ return datetime.now(timezone.utc).replace(microsecond=0)
+
+
+def parse_timestamp(value: str) -> datetime:
+ parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
+ if parsed.tzinfo is None:
+ raise ValueError(f"Timestamp must include a timezone: {value!r}")
+ return parsed.astimezone(timezone.utc).replace(microsecond=0)
+
+
+def format_timestamp(value: datetime) -> str:
+ return
value.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00",
"Z")
+
+
+def default_account_state() -> dict[str, Any]:
+ return {
+ "content_digest": None,
+ "status": AVAILABLE,
+ "last_failure_at": None,
+ "last_http_status": None,
+ "last_success_at": None,
+ "last_recovered_at": None,
+ "retry_after": None,
+ }
+
+
+def default_state() -> dict[str, Any]:
+ return {"version": STATE_VERSION, "next_account": 0, "accounts": {}}
+
+
+def load_state(path: Path) -> dict[str, Any]:
+ if not path.exists():
+ return default_state()
+
+ state = json.loads(path.read_text())
+ if not isinstance(state, dict):
+ raise ValueError("Authentication state must be a JSON object")
+ if state.get("version") != STATE_VERSION:
+ raise ValueError(f"Unsupported authentication state version:
{state.get('version')!r}")
+ if not isinstance(state.get("next_account"), int):
+ raise ValueError("Authentication state next_account must be an
integer")
+ if not isinstance(state.get("accounts"), dict):
+ raise ValueError("Authentication state accounts must be an object")
+ return state
+
+
+def save_state(path: Path, state: dict[str, Any]) -> None:
+ with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent,
delete=False) as temporary:
+ json.dump(state, temporary, indent=2, sort_keys=True)
+ temporary.write("\n")
+ temporary_path = Path(temporary.name)
+ temporary_path.replace(path)
+
+
+def parse_id_token(id_token: str) -> dict[str, Any]:
+ parts = id_token.split(".")
+ if len(parts) < 3 or any(not part for part in parts[:3]):
+ raise ValueError("Codex id_token must be a parseable JWT")
+ payload = parts[1]
+ try:
+ decoded = base64.b64decode(payload + "=" * (-len(payload) % 4),
altchars=b"-_", validate=True)
+ claims = json.loads(decoded)
+ except (ValueError, json.JSONDecodeError) as exc:
+ raise ValueError("Codex id_token must be a parseable JWT") from exc
+ if not isinstance(claims, dict):
+ raise ValueError("Codex id_token JWT payload must be an object")
+ return claims
+
+
+def string_claim(value: object, name: str) -> str | None:
+ if value is None:
+ return None
+ if not isinstance(value, str):
+ raise ValueError(f"Codex id_token {name} claim must be a string")
+ return value
+
+
+def auth_identity(credentials: dict[str, Any]) -> dict[str, Any]:
+ if credentials.get("auth_mode") != "chatgpt":
+ raise ValueError("Codex credentials must use ChatGPT authentication")
+
+ tokens = credentials.get("tokens")
+ if not isinstance(tokens, dict):
+ raise ValueError("Codex credentials must contain a tokens object")
+ for token_name in ("access_token", "refresh_token", "id_token"):
+ if not isinstance(tokens.get(token_name), str) or not
tokens[token_name]:
+ raise ValueError(f"Codex {token_name} must be a non-empty string")
+
+ claims = parse_id_token(tokens["id_token"])
+ profile = claims.get("https://api.openai.com/profile")
+ if profile is not None and not isinstance(profile, dict):
+ raise ValueError("Codex id_token profile claim must be an object")
+ auth_claims = claims.get("https://api.openai.com/auth")
+ if auth_claims is not None and not isinstance(auth_claims, dict):
+ raise ValueError("Codex id_token auth claim must be an object")
+ profile = profile or {}
+ auth_claims = auth_claims or {}
+ fedramp = auth_claims.get("chatgpt_account_is_fedramp", False)
+ if not isinstance(fedramp, bool):
+ raise ValueError("Codex id_token chatgpt_account_is_fedramp claim must
be a boolean")
+
+ email = claims.get("email")
+ if email is None:
+ email = profile.get("email")
+ return {
+ "email": string_claim(email, "email"),
+ "chatgpt_user_id": string_claim(
+ auth_claims.get("chatgpt_user_id", auth_claims.get("user_id")),
"chatgpt_user_id"
+ ),
+ "chatgpt_account_id":
string_claim(auth_claims.get("chatgpt_account_id"), "chatgpt_account_id"),
+ "chatgpt_account_is_fedramp": fedramp,
+ "account_id": string_claim(tokens.get("account_id"), "account_id"),
+ }
+
+
+def validate_auth(credentials: dict[str, Any]) -> None:
+ auth_identity(credentials)
+ last_refresh = credentials.get("last_refresh")
+ if last_refresh is not None:
+ if not isinstance(last_refresh, str) or not last_refresh:
+ raise ValueError("Codex last_refresh must be a non-empty
timestamp")
+ parse_timestamp(last_refresh)
+
+
+def promote_auth(baseline: dict[str, Any], candidate: dict[str, Any]) ->
dict[str, Any]:
+ validate_auth(baseline)
+ validate_auth(candidate)
+ if auth_identity(baseline) != auth_identity(candidate):
+ raise ValueError("Refreshed credentials changed an immutable account
or workspace claim")
+ if not isinstance(candidate.get("last_refresh"), str) or not
candidate["last_refresh"]:
+ raise ValueError("Refreshed credentials must include last_refresh")
+
+ promoted = copy.deepcopy(baseline)
+ for token_name in ("access_token", "refresh_token", "id_token"):
Review Comment:
[P1] Do not trust the agent-owned token bundle for promotion
Protecting the helper and state file does not protect this input: Codex owns
`auth.json`, can keep the baseline `id_token` unchanged, replace `access_token`
and `refresh_token` with any non-empty strings, and advance `last_refresh`.
`auth_identity` still matches, so this loop copies the attacker-controlled
values and the privileged parent uploads them to the shared OSS object. This is
a distinct remaining channel across the new UID boundary. Refresh/promote the
credential in a trusted broker (or validate the replacement with the provider),
rather than treating self-asserted JWT claims and non-empty token strings as
proof.
<!-- doris-codex-review:30511911292:1 -->
##########
.github/workflows/code-review-runner.yml:
##########
@@ -488,61 +707,460 @@ jobs:
HEAD_SHA: ${{ inputs.head_sha }}
BASE_SHA: ${{ inputs.base_sha }}
+ - name: Grant isolated Codex user access to review context
+ run: |
+ sudo chown -R "$CODEX_REVIEW_USER:$CODEX_REVIEW_USER"
"$REVIEW_CONTEXT_DIR"
+
- name: Run automated code review
id: review
timeout-minutes: 115
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OSS_AK: ${{ secrets.OSS_AK }}
+ OSS_SK: ${{ secrets.OSS_SK }}
+ OSS_ENDPOINT: oss-cn-hongkong.aliyuncs.com
+ OSS_CODEX_AUTH_STATUS_OBJECT:
oss://doris-community-ci/codex/auth-status.json
+ OSS_CODEX_REVIEW_PENDING_PREFIX:
oss://doris-community-ci/codex/review-pending
+ OSS_CODEX_REVIEW_CLAIMED_PREFIX:
oss://doris-community-ci/codex/review-claimed
+ AUTH_UNAVAILABLE: ${{ steps.configure_auth.outputs.auth_unavailable
}}
+ CONFIGURED_ALL_QUOTA_EXHAUSTED: ${{
steps.configure_auth.outputs.all_quota_exhausted }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ inputs.pr_number }}
HEAD_SHA: ${{ inputs.head_sha }}
+ BASE_SHA: ${{ inputs.base_sha }}
+ REVIEW_FOCUS: ${{ inputs.review_focus }}
+ DEFERRED_REQUEST_ID: ${{ inputs.deferred_request_id }}
+ DEFERRED_CLAIM_ID: ${{ inputs.deferred_claim_id }}
run: |
- GOAL_PROMPT="$(cat "$REVIEW_CONTEXT_DIR/codex_goal_prompt.txt")"
- review_started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ write_review_output() {
+ local key="$1"
+ local value="$2"
+ {
+ echo "${key}<<EOF"
+ printf '%s\n' "$value"
+ echo "EOF"
+ } >> "$GITHUB_OUTPUT"
+ }
+
+ OSSUTIL=/opt/doris-ci/ossutil
+
+ defer_review() {
+ local retry_at="$1" request_id request_object
+ request_id="${DEFERRED_REQUEST_ID:-$GITHUB_RUN_ID}"
+
request_object="$OSS_CODEX_REVIEW_PENDING_PREFIX/${PR_NUMBER}-${HEAD_SHA}-${request_id}.json"
+ local request_file
+ request_file="$(mktemp "$RUNNER_TEMP/codex-review-pending.XXXXXX")"
+ jq -n \
+ --arg pr_number "$PR_NUMBER" \
+ --arg head_sha "$HEAD_SHA" \
+ --arg base_sha "$BASE_SHA" \
+ --arg review_focus "$REVIEW_FOCUS" \
+ --arg not_before "$retry_at" \
+ --arg request_id "$request_id" \
+ --arg lease_id '' \
+ --arg lease_expires_at '' \
+ '{pr_number: $pr_number, head_sha: $head_sha, base_sha:
$base_sha,
+ review_focus: $review_focus, not_before: $not_before,
request_id: $request_id,
+ lease_id: $lease_id, lease_expires_at: $lease_expires_at}' >
"$request_file"
+ write_review_output "preserve_request" "true"
+ "$OSSUTIL" -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f \
+ "$request_file" "$request_object"
+ rm -f "$request_file"
+ write_review_output "deferred_review" "true"
+ write_review_output "successor_request_object" "$request_object"
+ }
+
+ auth_object_args=()
+ known_auth_object_args=()
+ while IFS= read -r auth_object; do
+ auth_object_args+=(--auth-object "$auth_object")
+ known_auth_object_args+=(--known-auth-object "$auth_object")
+ done < "$CODEX_AUTH_OBJECTS_FILE"
+ if [ "${#auth_object_args[@]}" -eq 0 ]; then
+ write_review_output "all_quota_exhausted" "false"
+ write_review_output "failure_reason" "No valid Codex auth account
is available for review."
+ exit 1
+ fi
- set +e
- # GitHub-hosted runners are ephemeral. Avoid workspace-write here
because
- # Codex uses bubblewrap for that mode and uid maps can be
unavailable.
- codex exec --goal "$GOAL_PROMPT" \
- --cd "$GITHUB_WORKSPACE" \
- --model "gpt-5.6-sol" \
- --config "model_reasoning_effort=xhigh" \
- --sandbox danger-full-access \
- --color never \
- --json \
- --output-last-message
"$REVIEW_CONTEXT_DIR/codex-final-message.txt" \
- > "$REVIEW_CONTEXT_DIR/codex-events.jsonl" \
- 2> >(tee "$REVIEW_CONTEXT_DIR/codex-stderr.log" >&2)
- status=$?
- set -e
+ sync_auth_state() {
+ "$OSSUTIL" -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f \
+ "$CODEX_AUTH_STATE_FILE" "$OSS_CODEX_AUTH_STATUS_OBJECT"
+ }
+
+ record_auth_result() {
+ local result="$1"
+ local http_status="$2"
+ local retry_after="$3"
+ local record_args=(
+ record
+ --state "$CODEX_AUTH_STATE_FILE"
+ --auth-object "$CODEX_AUTH_STATE_KEY"
+ "${known_auth_object_args[@]}"
+ --result "$result"
+ )
+ if [ -n "$http_status" ]; then
+ record_args+=(--http-status "$http_status")
+ fi
+ if [ -n "$retry_after" ]; then
+ record_args+=(--retry-after "$retry_after")
+ fi
+ "$CODEX_AUTH_STATE_HELPER" "${record_args[@]}"
+ }
+
+ install_selected_auth() {
+ local candidate_auth_file
+ candidate_auth_file="$(mktemp "$RUNNER_TEMP/codex-auth.XXXXXX")"
+ "$OSSUTIL" -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f \
Review Comment:
[P2] Reconcile the bytes that are actually installed
Discovery hashes A-old, but this function downloads the selected object
again without updating its digest. If an operator replaces that credential in
between, Codex runs with A-new while state still records A-old; a quota/auth
failure is then persisted against the old digest, and the next job sees A-new
as another replacement and resets the failure to `available`. Install the
already validated bytes, or hash and reconcile every installation before Codex
starts, and cover replacement-between-discovery-and-install.
<!-- doris-codex-review:30511911292:1 -->
##########
.github/scripts/test_codex_auth_state.py:
##########
@@ -0,0 +1,292 @@
+from __future__ import annotations
Review Comment:
[P2] Run the auth-state suite in CI
These 18 tests pass locally, but no repository workflow or test harness
invokes this file, so the rotation, promotion, and failure-classification
behavior can regress while CI remains green. Add a non-secret job (or an early
helper self-test) that runs this suite; the shell/workflow ordering still needs
separate integration coverage.
<!-- doris-codex-review:30511911292:1 -->
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]