This is an automated email from the ASF dual-hosted git repository.

zclllyybb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 1979bb02ac0 [fix](ci) Harden Codex review workflow (#64329)
1979bb02ac0 is described below

commit 1979bb02ac011b21a5a5ed9a92127b8817f22afe
Author: zclllyybb <[email protected]>
AuthorDate: Wed Jun 10 11:00:49 2026 +0800

    [fix](ci) Harden Codex review workflow (#64329)
    
    ## Proposed changes
    
    Follow-up to the merged Litefuse payload-size fix in #64328.
    
    This patch keeps the previous Litefuse 413/pagination/trace-shape fixes
    and adds the latest CI hardening:
    
    - Renames the automated review workflows from `opencode-*` to
    `code-review-*`, and removes the remaining `opencode` wording from
    workflow-local paths.
    - Removes the temporary gh-only / MCP-forbid prompt restrictions that
    were added while diagnosing the GitHub MCP failure path. The workflow
    still verifies the real outcome by reading GitHub Reviews for the
    current head SHA after Codex exits.
    - Enables Codex memories in CI and syncs `$CODEX_HOME/memories` with
    `oss://doris-community-ci/codex/memories.tar.gz` before and after
    review. Memory sync is best-effort and does not block review completion.
    - Keeps Litefuse recording best-effort so a successful code review is
    not failed only because trace upload or verification fails.
    
    The original payload-limit fixes are still included:
    
    - removes the workflow's 4KB context override;
    - makes `--max-context-json-chars <= 0` inherit `--max-json-chars`;
    - paginates legacy Litefuse observation readback during verification;
    - adaptively retries Litefuse 413 responses without creating extra
    traces;
    - preserves one trace per run and original observation ordering across
    ingestion chunks.
    
    ## Commits
    
    - `500ebc6319b` `[chore](ci) Rename code review workflows`
    - `57a0dd223e3` `[fix](ci) Relax code review submission tooling`
    - `746e46d0bbb` `[feat](ci) Sync Codex memories for reviews`
    
    ## Validation
    
    Fork workflow validation:
    
    - Pushed the same workflow head to `origin/master` first.
    - Opened temporary fork PR https://github.com/zclllyybb/doris/pull/33
    from `codex-review-validation-20260610-memory-rename` and triggered
    `/review`.
    - Code review run succeeded:
    https://github.com/zclllyybb/doris/actions/runs/27249379410/job/80470334233
    - GitHub review was submitted by `github-actions` on commit
    `b14d07483c2d4e2c3dd00a9e8da00e52a9fff4ac`.
    - Litefuse trace `0a88ea57df463c2f975fc0081a612f02` was uploaded and
    verified with trace input/output present, all agent-message context
    windows present, 60 events, 35 observations, no 413 retries, and one
    ingestion request under the configured payload cap.
    - First memory download saw the expected initial `NoSuchKey` for
    `codex/memories.tar.gz` and continued without failing the review; memory
    upload then succeeded with a 7,735-byte archive.
    - Temporary validation PR and branch were closed/deleted after
    validation.
    
    Earlier payload-limit validation retained from the previous iteration:
    
    - Unit-style chunking check: splitting into multiple chunks preserved
    event bodies, event order, and trace id.
    - Simulated Litefuse 413 on multi-event batches: uploader split/retried,
    preserved event order, and uploaded all 16 events successfully.
    - Simulated Litefuse 413 on one oversized observation: uploader shrank
    the single observation and retried successfully.
    - Synthetic dry-run with 267 Codex events and default context
    truncation: split into 24 requests, max request size 395324 bytes under
    a 400000-byte test cap.
    - Synthetic dry-run with a single huge observation and default context
    truncation: max request size 341060 bytes under a 400000-byte test cap.
    - Verified the failed trace from
    https://github.com/apache/doris/actions/runs/27213447314/job/80351165150
    using paginated legacy readback: trace
    `45202ab41ddbfd099877fa7be5cc1256` has 105 observations across pages, 21
    agent messages, one turn input, and zero observations missing I/O.
    - Diagnosed
    https://github.com/apache/doris/actions/runs/27213705642/job/80352669355
    as a live Litefuse 413 from the old master helper
    `f48efb28db486837db5375e6fd0bcfc95ea67a05` despite `--max-payload-bytes
    4000000`.
---
 .github/scripts/emit_litefuse_otel_io.py           | 98 ++++++++++++++++++----
 ...-review-comment.yml => code-review-comment.yml} |  2 +-
 ...de-review-runner.yml => code-review-runner.yml} | 61 +++++++++++---
 ...sync-result.yml => code-review-sync-result.yml} |  0
 4 files changed, 135 insertions(+), 26 deletions(-)

diff --git a/.github/scripts/emit_litefuse_otel_io.py 
b/.github/scripts/emit_litefuse_otel_io.py
index 4a93d5262d4..351b5b5cbf3 100644
--- a/.github/scripts/emit_litefuse_otel_io.py
+++ b/.github/scripts/emit_litefuse_otel_io.py
@@ -23,6 +23,7 @@ import json
 import os
 import secrets
 import time
+import urllib.error
 import urllib.parse
 import urllib.request
 
@@ -521,18 +522,29 @@ def shrink_event_for_payload(event, max_payload_bytes):
     body = shrunk.get("body") if isinstance(shrunk.get("body"), dict) else {}
     event_name = body.get("name")
 
-    for max_chars in (2_000, 1_000, 500, 200, 80):
+    def candidate_with_limits(max_chars, max_context_events=None):
         candidate = json.loads(json.dumps(shrunk, ensure_ascii=False))
         candidate_body = candidate.get("body") if 
isinstance(candidate.get("body"), dict) else {}
         input_object = candidate_body.get("input")
         if isinstance(input_object, dict):
             context_events = 
input_object.get("events_since_previous_agent_message")
             if isinstance(context_events, list):
-                input_object["events_since_previous_agent_message"] = [
+                compact_events = [
                     compact_context_event(context_event, max_chars)
                     for context_event in context_events
                     if isinstance(context_event, dict)
                 ]
+                if max_context_events is not None:
+                    omitted_count = max(len(compact_events) - 
max_context_events, 0)
+                    if max_context_events <= 0:
+                        compact_events = []
+                    else:
+                        compact_events = compact_events[-max_context_events:]
+                    if omitted_count:
+                        input_object[
+                            "events_since_previous_agent_message_omitted_count"
+                        ] = omitted_count
+                input_object["events_since_previous_agent_message"] = 
compact_events
             if isinstance(input_object.get("previous_agent_message"), dict):
                 previous_message = input_object["previous_agent_message"]
                 previous_message["text"] = truncate_text(
@@ -557,10 +569,20 @@ def shrink_event_for_payload(event, max_payload_bytes):
         metadata = candidate_body.get("metadata")
         if metadata not in (None, ""):
             candidate_body["metadata"] = truncate_json(metadata, max_chars)
+        return candidate
+
+    for max_chars in (2_000, 1_000, 500, 200, 80):
+        candidate = candidate_with_limits(max_chars)
 
         if json_payload_bytes({"batch": [candidate]}) <= max_payload_bytes:
             return candidate
 
+    for max_context_events in (50, 20, 10, 5, 2, 1, 0):
+        for max_chars in (80, 40, 20, 10):
+            candidate = candidate_with_limits(max_chars, max_context_events)
+            if json_payload_bytes({"batch": [candidate]}) <= max_payload_bytes:
+                return candidate
+
     raise RuntimeError(
         "Litefuse ingestion event is too large after truncation: "
         f"{json_payload_bytes({'batch': [event]})} bytes > {max_payload_bytes} 
bytes; "
@@ -622,21 +644,51 @@ def post_payload_once(endpoint, public_key, secret_key, 
payload):
         }
 
 
+def retry_payload_chunks_after_413(payload, request_size, max_payload_bytes):
+    batch = payload.get("batch") or []
+    if not batch:
+        raise RuntimeError(
+            "Litefuse ingestion returned 413 for an empty payload chunk"
+        )
+
+    next_limit = max(1_000, min(max_payload_bytes - 1, request_size // 2))
+    if len(batch) == 1:
+        event = shrink_event_for_payload(batch[0], next_limit)
+        return [({"batch": [event]}, json_payload_bytes({"batch": [event]}))]
+
+    return chunk_payload(payload, next_limit)
+
+
 def post_payload(endpoint, public_key, secret_key, payload, max_payload_bytes):
     statuses = []
     success_count = 0
     request_sizes = []
+    retry_count = 0
     chunks = chunk_payload(payload, max_payload_bytes)
-    for chunk, request_size in chunks:
-        status = post_payload_once(endpoint, public_key, secret_key, chunk)
+    while chunks:
+        chunk, request_size = chunks.pop(0)
+        try:
+            status = post_payload_once(endpoint, public_key, secret_key, chunk)
+        except urllib.error.HTTPError as exc:
+            if exc.code != 413:
+                raise
+            retry_count += 1
+            chunks = (
+                retry_payload_chunks_after_413(
+                    chunk, request_size, max_payload_bytes
+                )
+                + chunks
+            )
+            continue
         statuses.append(status["status"])
         success_count += int(status.get("success_count") or 0)
         request_sizes.append(request_size)
     return {
         "statuses": statuses,
-        "request_count": len(chunks),
+        "request_count": len(statuses),
         "request_sizes": request_sizes,
         "max_request_size": max(request_sizes) if request_sizes else 0,
+        "payload_too_large_retries": retry_count,
         "success_count": success_count,
     }
 
@@ -673,16 +725,30 @@ def fetch_observations_v2(base_url, public_key, 
secret_key, trace_id):
         return json.loads(response.read().decode())
 
 
-def fetch_observations_legacy(base_url, public_key, secret_key, trace_id):
+def fetch_observations_legacy(
+    base_url, public_key, secret_key, trace_id, max_pages=10
+):
     auth = base64.b64encode(f"{public_key}:{secret_key}".encode()).decode()
-    params = urllib.parse.urlencode({"traceId": trace_id, "limit": "100", 
"page": "1"})
-    request = urllib.request.Request(
-        f"{base_url.rstrip('/')}/api/public/observations?{params}",
-        headers={"Authorization": f"Basic {auth}"},
-        method="GET",
-    )
-    with urllib.request.urlopen(request, timeout=30) as response:
-        return json.loads(response.read().decode())
+    limit = 100
+    rows = []
+    last_payload = {}
+    for page in range(1, max_pages + 1):
+        params = urllib.parse.urlencode(
+            {"traceId": trace_id, "limit": str(limit), "page": str(page)}
+        )
+        request = urllib.request.Request(
+            f"{base_url.rstrip('/')}/api/public/observations?{params}",
+            headers={"Authorization": f"Basic {auth}"},
+            method="GET",
+        )
+        with urllib.request.urlopen(request, timeout=30) as response:
+            payload = json.loads(response.read().decode())
+        last_payload = payload if isinstance(payload, dict) else {}
+        page_rows = observation_rows_from_v2(last_payload)
+        rows.extend(page_rows)
+        if len(page_rows) < limit:
+            break
+    return {**last_payload, "data": rows}
 
 
 def observation_rows_from_v2(payload):
@@ -908,7 +974,7 @@ def parse_args():
     parser.add_argument("--max-input-chars", type=int, default=200_000)
     parser.add_argument("--max-output-chars", type=int, default=200_000)
     parser.add_argument("--max-json-chars", type=int, default=40_000)
-    parser.add_argument("--max-context-json-chars", type=int, default=4_000)
+    parser.add_argument("--max-context-json-chars", type=int, default=0)
     parser.add_argument("--max-payload-bytes", type=int, default=4_000_000)
     parser.add_argument("--verify", action="store_true")
     parser.add_argument("--dry-run", action="store_true")
@@ -922,6 +988,8 @@ def parse_args():
 def main():
     args = parse_args()
     endpoint = args.endpoint or 
f"{args.base_url.rstrip('/')}/api/public/ingestion"
+    if args.max_context_json_chars <= 0:
+        args.max_context_json_chars = args.max_json_chars
 
     input_text = read_text(args.input_file, args.max_input_chars)
     output_text = (
diff --git a/.github/workflows/opencode-review-comment.yml 
b/.github/workflows/code-review-comment.yml
similarity index 98%
rename from .github/workflows/opencode-review-comment.yml
rename to .github/workflows/code-review-comment.yml
index d042da95ac5..89eea813b40 100644
--- a/.github/workflows/opencode-review-comment.yml
+++ b/.github/workflows/code-review-comment.yml
@@ -59,7 +59,7 @@ jobs:
         github.event.comment.author_association == 'OWNER' ||
         github.event.comment.author_association == 'COLLABORATOR'
       )
-    uses: ./.github/workflows/opencode-review-runner.yml
+    uses: ./.github/workflows/code-review-runner.yml
     secrets: inherit
     with:
       pr_number: ${{ needs.resolve-pr.outputs.pr_number }}
diff --git a/.github/workflows/opencode-review-runner.yml 
b/.github/workflows/code-review-runner.yml
similarity index 90%
rename from .github/workflows/opencode-review-runner.yml
rename to .github/workflows/code-review-runner.yml
index 44d4db3d8e2..99baccae58e 100644
--- a/.github/workflows/opencode-review-runner.yml
+++ b/.github/workflows/code-review-runner.yml
@@ -76,6 +76,13 @@ jobs:
           cli_auth_credentials_store = "file"
           approval_policy = "never"
 
+          [features]
+          memories = true
+
+          [memories]
+          use_memories = true
+          generate_memories = true
+
           [shell_environment_policy]
           inherit = "all"
 
@@ -92,9 +99,32 @@ jobs:
           OSS_ENDPOINT: oss-cn-hongkong.aliyuncs.com
           OSS_CODEX_AUTH_OBJECT: oss://doris-community-ci/codex/auth.json
 
+      - name: Sync Codex memories from OSS
+        continue-on-error: true
+        run: |
+          install -m 700 -d "$CODEX_HOME/memories"
+          archive="$RUNNER_TEMP/codex-memories.tar.gz"
+
+          if ossutil -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f 
"$OSS_CODEX_MEMORIES_OBJECT" "$archive"; then
+            tar -tzf "$archive" | awk '
+              $0 != "memories" && $0 !~ /^memories\// { bad = 1 }
+              END { exit bad }
+            '
+            tar -xzf "$archive" -C "$CODEX_HOME"
+            install -m 700 -d "$CODEX_HOME/memories"
+            chmod -R go-rwx "$CODEX_HOME/memories" || true
+          else
+            echo "No Codex memories archive could be downloaded; continuing 
with empty memories."
+          fi
+        env:
+          OSS_AK: ${{ secrets.OSS_AK }}
+          OSS_SK: ${{ secrets.OSS_SK }}
+          OSS_ENDPOINT: oss-cn-hongkong.aliyuncs.com
+          OSS_CODEX_MEMORIES_OBJECT: 
oss://doris-community-ci/codex/memories.tar.gz
+
       - name: Prepare review context directory
         run: |
-          review_context_dir="$(mktemp -d 
"$GITHUB_WORKSPACE/.opencode-review.XXXXXX")"
+          review_context_dir="$(mktemp -d 
"$GITHUB_WORKSPACE/.code-review.XXXXXX")"
           review_context_rel="$(basename "$review_context_dir")"
           printf 'REVIEW_CONTEXT_DIR=%s\n' "$review_context_dir" >> 
"$GITHUB_ENV"
           printf 'REVIEW_CONTEXT_REL=%s\n' "$review_context_rel" >> 
"$GITHUB_ENV"
@@ -175,9 +205,7 @@ jobs:
           You are performing an automated code review inside a GitHub Actions 
runner. The gh CLI is available and authenticated via GH_TOKEN.
           The current directory is the code repository for the PR to be 
reviewed.
           You MUST NOT attempt to access any files outside the current 
directory. and you DO NOT need to. But this does not prevent you from normally 
using any skill or web fetch tools.
-          You MUST use the local gh CLI, including gh api when needed, for 
pull request review submission.
-          Do NOT use GitHub MCP connector write tools such as 
github_add_review_to_pr.
-          You can comment on the pull request through the GH_TOKEN-backed gh 
CLI.
+          You can comment on the pull request.
           Proceed with all subsequent research at the HIGHEST level of 
thought, aiming to identify all issues and submit all comments in JSON format.
 
           Context:
@@ -267,11 +295,6 @@ jobs:
             fi
           fi
 
-          if [ -z "$failure_reason" ] \
-            && jq -e 'select(.type == "item.completed" and (.item // {}).type 
== "mcp_tool_call" and (.item // {}).tool == "github_add_review_to_pr")' 
"$REVIEW_CONTEXT_DIR/codex-events.jsonl" >/dev/null; then
-            failure_reason="Codex attempted to submit the review through the 
GitHub MCP connector instead of the GH_TOKEN-backed gh CLI."
-          fi
-
           if [ -z "$failure_reason" ]; then
             reviews_file="$REVIEW_CONTEXT_DIR/pr_reviews_after_codex.json"
             reviews_api_ok=false
@@ -309,6 +332,7 @@ jobs:
 
       - name: Record review I/O to Litefuse
         if: ${{ always() }}
+        continue-on-error: true
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           LANGFUSE_PUBLIC_KEY: ${{ secrets.LANGFUSE_PK }}
@@ -347,7 +371,6 @@ jobs:
             --model "gpt-5.5" \
             --reasoning-effort "$REASONING_EFFORT" \
             --environment "github-actions" \
-            --max-context-json-chars 4000 \
             --max-payload-bytes 4000000 \
             --min-observations 4 \
             --min-step-observations 2 \
@@ -355,6 +378,24 @@ jobs:
             --verify-sleep-seconds 5 \
             --verify
 
+      - name: Sync Codex memories back to OSS
+        if: ${{ always() }}
+        continue-on-error: true
+        run: |
+          if [ ! -d "$CODEX_HOME/memories" ]; then
+            echo "No Codex memories directory found; skipping memories sync."
+            exit 0
+          fi
+
+          archive="$RUNNER_TEMP/codex-memories.tar.gz"
+          tar -C "$CODEX_HOME" -czf "$archive" memories
+          ossutil -i "$OSS_AK" -k "$OSS_SK" -e "$OSS_ENDPOINT" cp -f 
"$archive" "$OSS_CODEX_MEMORIES_OBJECT"
+        env:
+          OSS_AK: ${{ secrets.OSS_AK }}
+          OSS_SK: ${{ secrets.OSS_SK }}
+          OSS_ENDPOINT: oss-cn-hongkong.aliyuncs.com
+          OSS_CODEX_MEMORIES_OBJECT: 
oss://doris-community-ci/codex/memories.tar.gz
+
       - name: Sync Codex auth back to OSS
         if: ${{ always() }}
         run: |
diff --git a/.github/workflows/opencode-review-sync-result.yml 
b/.github/workflows/code-review-sync-result.yml
similarity index 100%
rename from .github/workflows/opencode-review-sync-result.yml
rename to .github/workflows/code-review-sync-result.yml


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to