fix: deploy Jules workflow improvements - #379
Conversation
…, dedup, artifact upload)
|
|
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Reviewer's GuideThis PR hardens and enriches all Jules PR workflows by adding resilient session creation with retries, longer polling windows, richer agent message capture, artifact preservation of outputs, cross-session finding deduplication, and optional feedback summary posting via an external tool. Sequence diagram for resilient Jules session creation with retriessequenceDiagram
participant Workflow as GithubActionsWorkflow
participant JulesAPI
Workflow->>JulesAPI: curl POST /v1alpha/sessions
loop up to MAX_RETRIES (3)
alt curl succeeded (CURL_EXIT == 0)
JulesAPI-->>Workflow: RESPONSE (session created)
else curl failed
Workflow->>Workflow: ERROR_CODE = jq .error.code
Workflow->>Workflow: ERROR_MSG = jq .error.message
alt quota or rate limited (429/403/quota keywords)
Workflow->>Workflow: echo quota_exhausted=true
Workflow->>Workflow: exit 0 (non-retryable)
else other error
alt attempt < MAX_RETRIES
Workflow->>Workflow: sleep RETRY_DELAY
Workflow->>Workflow: RETRY_DELAY *= 2
Workflow->>JulesAPI: curl POST /v1alpha/sessions
else last attempt
Workflow->>Workflow: echo Error after MAX_RETRIES
Workflow->>Workflow: exit 1
end
end
end
end
Workflow->>Workflow: SESSION_NAME, SESSION_ID = jq from RESPONSE
Sequence diagram for cross-session findings deduplication and trackingsequenceDiagram
participant Workflow as GithubActionsWorkflow
participant GitHubAPI
Workflow->>Workflow: findingRegex.exec(reviewText/mainReview)
Workflow->>Workflow: build comments[] and seen (per-run)
Workflow->>GitHubAPI: github.paginate(issues.listComments)
GitHubAPI-->>Workflow: allComments
alt existing findings tracking comment
Workflow->>Workflow: prevFindingsComment = find body includes <!-- jules-posted-findings -->
Workflow->>Workflow: prevFindings = JSON.parse(prevFindingsComment.body suffix)
Workflow->>Workflow: add keys to seen Set
else no tracking comment
Workflow->>Workflow: seen contains only current run findings
end
Workflow->>Workflow: postedFindings[path:line] = true for each comment
Workflow->>Workflow: findingsBody = "<!-- jules-posted-findings -->\n" + JSON.stringify(postedFindings)
alt prevFindingsComment exists
Workflow->>GitHubAPI: github.rest.issues.updateComment(body = findingsBody)
GitHubAPI-->>Workflow: updatedComment
else no previous comment
Workflow->>GitHubAPI: github.rest.issues.createComment(body = findingsBody)
GitHubAPI-->>Workflow: newComment
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
WalkthroughSix Jules workflows add retry handling for session creation, longer polling windows, aggregated agent output, artifact uploads, flexible finding parsing, cross-session deduplication, and optional feedback posting. ChangesJules workflow execution
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In the jq calls that build the concatenated agent messages, you changed from
[(.activities // [])[] ...]to[.activities[] ...]; this will now fail if.activitiesis null or missing, so consider restoring the(.activities // [])[]pattern (or equivalent) across all workflows to keep the parsing robust. - The cross-session dedup logic overwrites the
<!-- jules-posted-findings -->tracking comment with only the findings from the current run, which drops keys from older runs and can allow previously posted findings to be re-emitted later; consider merging the newpostedFindingsinto the previously stored map instead of replacing it.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the jq calls that build the concatenated agent messages, you changed from `[(.activities // [])[] ...]` to `[.activities[] ...]`; this will now fail if `.activities` is null or missing, so consider restoring the `(.activities // [])[]` pattern (or equivalent) across all workflows to keep the parsing robust.
- The cross-session dedup logic overwrites the `<!-- jules-posted-findings -->` tracking comment with only the findings from the current run, which drops keys from older runs and can allow previously posted findings to be re-emitted later; consider merging the new `postedFindings` into the previously stored map instead of replacing it.
## Individual Comments
### Comment 1
<location path=".github/workflows/jules-pr-review.yml" line_range="565-570" />
<code_context>
}
+ // Save posted findings for cross-session dedup
+ const postedFindings = {};
+ for (const c of comments) {
+ postedFindings[`${c.path}:${c.line}`] = true;
+ }
+ // Update or create the findings tracking comment
+ const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`;
+ if (prevFindingsComment) {
+ await github.rest.issues.updateComment({
</code_context>
<issue_to_address>
**issue (bug_risk):** Updating the findings tracking comment discards previously posted findings when there are no new comments in this run.
`seen` is initialized from `prevFindings`, but `postedFindings` is rebuilt only from the current run’s `comments`. When there are no new `comments`, you overwrite the tracking comment with `{}`, erasing history and allowing duplicates in later runs.
To keep cross-run deduplication, initialize `postedFindings` from `prevFindings` and then add new entries:
```js
const postedFindings = prevFindings || {};
for (const c of comments) {
postedFindings[`${c.path}:${c.line}`] = true;
}
```
This way existing findings are preserved and new ones are appended.
</issue_to_address>
### Comment 2
<location path=".github/workflows/jules-pr-review.yml" line_range="400-406" />
<code_context>
echo "session_state=TIMEOUT" >> "$GITHUB_OUTPUT"
echo "Force-review timed out." > /tmp/jules-force-review-output.txt
+ - name: Upload review output as artifact
+ if: always() && steps.prepare.outputs.skip != 'true' && steps.create.outputs.quota_exhausted != 'true'
+ uses: actions/upload-artifact@v4
+ with:
+ name: jules-review-output-${{ github.event.pull_request.number }}
+ path: /tmp/jules-*-output.txt
+ retention-days: 30
+
</code_context>
<issue_to_address>
**suggestion:** Uploading `/tmp/jules-*-output.txt` unconditionally can fail the step if no matching files exist.
If the job exits before any output file is written, this `always()` step can cause `actions/upload-artifact` to fail when the glob matches nothing, obscuring the original failure. Consider guarding the upload with a file-existence check (e.g., a small shell script around `ls /tmp/jules-*-output.txt`) or an expression like `if: hashFiles('/tmp/jules-*-output.txt') != ''` so the artifact step is skipped when no files exist.
```suggestion
- name: Upload review output as artifact
if: always() && steps.prepare.outputs.skip != 'true' && steps.create.outputs.quota_exhausted != 'true'
uses: actions/upload-artifact@v4
with:
name: jules-review-output-${{ github.event.pull_request.number }}
path: /tmp/jules-*-output.txt
retention-days: 30
if-no-files-found: ignore
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const postedFindings = {}; | ||
| for (const c of comments) { | ||
| postedFindings[`${c.path}:${c.line}`] = true; | ||
| } | ||
| // Update or create the findings tracking comment | ||
| const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`; |
There was a problem hiding this comment.
issue (bug_risk): Updating the findings tracking comment discards previously posted findings when there are no new comments in this run.
seen is initialized from prevFindings, but postedFindings is rebuilt only from the current run’s comments. When there are no new comments, you overwrite the tracking comment with {}, erasing history and allowing duplicates in later runs.
To keep cross-run deduplication, initialize postedFindings from prevFindings and then add new entries:
const postedFindings = prevFindings || {};
for (const c of comments) {
postedFindings[`${c.path}:${c.line}`] = true;
}This way existing findings are preserved and new ones are appended.
| - name: Upload review output as artifact | ||
| if: always() && steps.prepare.outputs.skip != 'true' && steps.create.outputs.quota_exhausted != 'true' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: jules-review-output-${{ github.event.pull_request.number }} | ||
| path: /tmp/jules-*-output.txt | ||
| retention-days: 30 |
There was a problem hiding this comment.
suggestion: Uploading /tmp/jules-*-output.txt unconditionally can fail the step if no matching files exist.
If the job exits before any output file is written, this always() step can cause actions/upload-artifact to fail when the glob matches nothing, obscuring the original failure. Consider guarding the upload with a file-existence check (e.g., a small shell script around ls /tmp/jules-*-output.txt) or an expression like if: hashFiles('/tmp/jules-*-output.txt') != '' so the artifact step is skipped when no files exist.
| - name: Upload review output as artifact | |
| if: always() && steps.prepare.outputs.skip != 'true' && steps.create.outputs.quota_exhausted != 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jules-review-output-${{ github.event.pull_request.number }} | |
| path: /tmp/jules-*-output.txt | |
| retention-days: 30 | |
| - name: Upload review output as artifact | |
| if: always() && steps.prepare.outputs.skip != 'true' && steps.create.outputs.quota_exhausted != 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jules-review-output-${{ github.event.pull_request.number }} | |
| path: /tmp/jules-*-output.txt | |
| retention-days: 30 | |
| if-no-files-found: ignore |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
.github/workflows/jules-pr-review.yml (1)
473-503: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDedup key (
file:line) ignores finding content — recurring-but-different findings at the same line get silently suppressed.If a resolved finding's file:line is later reused by an unrelated new finding,
seen.has(key)will skip it forever since the key doesn't encode severity/reason or a content hash.♻️ Suggested key including content signal
- const key = `${file}:${line}`; + const key = `${file}:${line}:${severity}`;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/jules-pr-review.yml around lines 473 - 503, Update the deduplication key used with seen in the finding parser to include a stable content signal, such as severity and reason (or a hash of the finding content), alongside file and line. Ensure unrelated findings reused at the same file:line are processed independently while identical findings remain deduplicated across sessions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/jules-pr-auto-fix.yml:
- Around line 255-260: Update the error-classification blocks in
.github/workflows/jules-pr-auto-fix.yml lines 255-260 and
.github/workflows/jules-pr-force-review.yml lines 303-308 to mark
quota_exhausted only for explicitly identified quota conditions, removing the
unconditional 403 classification. Preserve successful exit and the “session cap
reached” message for quota errors, but make non-quota 403 responses fail and
surface the configuration or access error.
In @.github/workflows/jules-pr-force-review.yml:
- Line 356: Increase the workflow job’s timeout-minutes setting to exceed the
full polling budget represented by MAX_ATTEMPTS, including retries, HTTP calls,
artifact upload, and result posting; use a value of at least 40 minutes so runs
can complete their timeout handling.
- Line 370: Update the jq expression that builds the review output so missing or
null .activities is treated as an empty array before iterating. Preserve the
existing agent-message filtering and joining behavior, ensuring sessions without
activities produce exactly the "Review unavailable" fallback.
- Around line 513-519: Update the posted-findings state construction to preserve
keys previously loaded into seen, then merge the current comments’ path-and-line
keys into that existing state before generating findingsBody. Keep newly posted
findings deduplicated while retaining all prior entries across sessions.
In @.github/workflows/jules-pr-rebuild.yml:
- Around line 395-427: Make the POST session-creation loop idempotent by
generating and sending a stable idempotency key for the same rebuild attempt, or
checking for an existing session before retrying. Update the curl request within
the retry loop so delayed success from an earlier request cannot create a
duplicate session, while preserving the existing retry and quota handling.
- Around line 174-206: The session-creation retry loop around the Jules POST can
duplicate sessions when a request succeeds but its response is lost. Replace the
blind retries in the `for attempt` block with a durable pre-POST guard or
supported idempotency mechanism, ensuring the workflow does not issue another
`POST /v1alpha/sessions` after the first attempt may have created a session.
In @.github/workflows/jules-pr-resolve-conflicts.yml:
- Around line 377-409: Update the session-creation retry loop around the POST to
prevent duplicate Jules sessions when a timeout or connection reset leaves the
request outcome unknown. Add and send a stable idempotency token with the
request, or disable retries for transport failures until the previous attempt’s
outcome is known; preserve quota/rate-limit handling and bounded retries for
safely retryable failures.
In @.github/workflows/jules-pr-review.yml:
- Line 376: Reduce MAX_ATTEMPTS in the polling logic so the 15-second polling
budget leaves sufficient slack within the job’s 30-minute timeout for setup,
retries, artifact upload, review posting, and feedback-summary processing. Apply
the same adjusted budget to the corresponding workflow if it shares this polling
configuration.
- Around line 563-586: Update the postedFindings construction in the
cross-session dedup save flow to parse and preserve the existing findings from
prevFindingsComment before adding the current run’s comments. Merge current keys
into that prior state, including when comments is empty, then serialize the
merged object for the existing updateComment/createComment calls so previously
tracked findings are never discarded.
- Around line 307-339: Make the retry logic around the session-creation POST
idempotent by preventing blind retries after failures that may have reached the
server. Before retrying, list the target PR’s existing Jules sessions and reuse
or stop when a matching session is found; otherwise restrict retries to clearly
client-side failures known not to have processed the request. Preserve the
existing quota detection and final failure behavior in the retry loop.
In @.github/workflows/jules-pr-walkthrough.yml:
- Line 273: Reduce MAX_ATTEMPTS in the polling configuration so the 15-second
polling budget leaves sufficient slack within the job’s 30-minute timeout for
setup, session retries, artifact upload, and comment posting. Apply the same
adjusted budget to the corresponding polling configuration in the related
workflow to keep both jobs consistent.
- Around line 203-235: Make the session-creation flow around the retry loop safe
for repeated POST attempts by supplying an API-supported stable idempotency or
request-parameters key derived from the same PR/payload, or remove retries and
use a deterministic failure/defer path. Ensure repeated attempts cannot create
duplicate walkthrough sessions.
---
Nitpick comments:
In @.github/workflows/jules-pr-review.yml:
- Around line 473-503: Update the deduplication key used with seen in the
finding parser to include a stable content signal, such as severity and reason
(or a hash of the finding content), alongside file and line. Ensure unrelated
findings reused at the same file:line are processed independently while
identical findings remain deduplicated across sessions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 164e5210-f072-44ac-a0df-d83c5e3708d4
📒 Files selected for processing (6)
.github/workflows/jules-pr-auto-fix.yml.github/workflows/jules-pr-force-review.yml.github/workflows/jules-pr-rebuild.yml.github/workflows/jules-pr-resolve-conflicts.yml.github/workflows/jules-pr-review.yml.github/workflows/jules-pr-walkthrough.yml
| # Quota/rate-limit is non-retryable — exit immediately | ||
| if [ "$ERROR_CODE" = "429" ] || [ "$ERROR_CODE" = "403" ] || echo "$ERROR_MSG" | grep -qiE 'quota|limit|exceeded|capacity|daily'; then | ||
| echo "quota_exhausted=true" >> "$GITHUB_OUTPUT" | ||
| echo "⚠️ Session cap reached — no session created." | ||
| exit 0 | ||
| fi |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not classify every 403 as quota exhaustion. A 403 can indicate invalid credentials or denied API access; both workflows exit successfully and post a misleading “session cap reached” message instead of surfacing the configuration failure.
.github/workflows/jules-pr-auto-fix.yml#L255-L260: only mark quota exhaustion for an explicitly identified quota condition; fail non-quota 403 responses..github/workflows/jules-pr-force-review.yml#L303-L308: apply the same precise classification.
📍 Affects 2 files
.github/workflows/jules-pr-auto-fix.yml#L255-L260(this comment).github/workflows/jules-pr-force-review.yml#L303-L308
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-auto-fix.yml around lines 255 - 260, Update the
error-classification blocks in .github/workflows/jules-pr-auto-fix.yml lines
255-260 and .github/workflows/jules-pr-force-review.yml lines 303-308 to mark
quota_exhausted only for explicitly identified quota conditions, removing the
unconditional 403 classification. Preserve successful exit and the “session cap
reached” message for quota errors, but make non-quota 403 responses fail and
surface the configuration or access error.
| run: | | ||
| SESSION_RESOURCE="${SESSION_NAME:-sessions/${SESSION_ID}}" | ||
| MAX_ATTEMPTS=60 | ||
| MAX_ATTEMPTS=120 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Increase the job timeout with the polling budget.
Line 356 allows 30 minutes of sleeps alone; session creation retries, HTTP calls, artifact upload, and posting occur within the same 30-minute job. Runs that need the full polling window are cancelled before writing TIMEOUT or posting results. Raise timeout-minutes above the full budget (for example, 40 minutes).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-force-review.yml at line 356, Increase the
workflow job’s timeout-minutes setting to exceed the full polling budget
represented by MAX_ATTEMPTS, including retries, HTTP calls, artifact upload, and
result posting; use a value of at least 40 minutes so runs can complete their
timeout handling.
| "https://jules.googleapis.com/v1alpha/${SESSION_RESOURCE}/activities?pageSize=100" \ | ||
| -H "X-Goog-Api-Key: ${JULES_API_KEY}") | ||
| echo "$ACTS" | jq -r '[(.activities // [])[] | select(.agentMessaged.agentMessage != null)][-1].agentMessaged.agentMessage // "Review unavailable"' > /tmp/jules-force-review-output.txt | ||
| echo "$ACTS" | jq -r '[.activities[] | select(.agentMessaged.agentMessage != null) | .agentMessaged.agentMessage] | if length == 0 then "Review unavailable" else join("\n\n---\n\n") end' > /tmp/jules-force-review-output.txt |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Handle sessions with no activities before iterating.
.activities[] fails when the field is absent, so the fallback is never reached. A failed or empty session should still produce Review unavailable.
Proposed fix
- echo "$ACTS" | jq -r '[.activities[] | select(.agentMessaged.agentMessage != null) | .agentMessaged.agentMessage] | if length == 0 then "Review unavailable" else join("\n\n---\n\n") end' > /tmp/jules-force-review-output.txt
+ echo "$ACTS" | jq -r '[ (.activities // [])[] | select(.agentMessaged.agentMessage != null) | .agentMessaged.agentMessage] | if length == 0 then "Review unavailable" else join("\n\n---\n\n") end' > /tmp/jules-force-review-output.txt📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| echo "$ACTS" | jq -r '[.activities[] | select(.agentMessaged.agentMessage != null) | .agentMessaged.agentMessage] | if length == 0 then "Review unavailable" else join("\n\n---\n\n") end' > /tmp/jules-force-review-output.txt | |
| echo "$ACTS" | jq -r '[ (.activities // [])[] | select(.agentMessaged.agentMessage != null) | .agentMessaged.agentMessage] | if length == 0 then "Review unavailable" else join("\n\n---\n\n") end' > /tmp/jules-force-review-output.txt |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-force-review.yml at line 370, Update the jq
expression that builds the review output so missing or null .activities is
treated as an empty array before iterating. Preserve the existing agent-message
filtering and joining behavior, ensuring sessions without activities produce
exactly the "Review unavailable" fallback.
| // Save posted findings for cross-session dedup | ||
| const postedFindings = {}; | ||
| for (const c of comments) { | ||
| postedFindings[`${c.path}:${c.line}`] = true; | ||
| } | ||
| // Update or create the findings tracking comment | ||
| const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve prior finding keys when updating state.
comments contains only findings newly posted this run; overwriting the tracking comment with it drops every key loaded into seen on Lines 451-456. Findings will reappear on alternating sessions.
Proposed fix
- const postedFindings = {};
- for (const c of comments) {
- postedFindings[`${c.path}:${c.line}`] = true;
- }
+ const postedFindings = {};
+ for (const key of seen) {
+ postedFindings[key] = true;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Save posted findings for cross-session dedup | |
| const postedFindings = {}; | |
| for (const c of comments) { | |
| postedFindings[`${c.path}:${c.line}`] = true; | |
| } | |
| // Update or create the findings tracking comment | |
| const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`; | |
| // Save posted findings for cross-session dedup | |
| const postedFindings = {}; | |
| for (const key of seen) { | |
| postedFindings[key] = true; | |
| } | |
| // Update or create the findings tracking comment | |
| const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-force-review.yml around lines 513 - 519, Update
the posted-findings state construction to preserve keys previously loaded into
seen, then merge the current comments’ path-and-line keys into that existing
state before generating findingsBody. Keep newly posted findings deduplicated
while retaining all prior entries across sessions.
| MAX_RETRIES=3 | ||
| RETRY_DELAY=5 | ||
| for attempt in $(seq 1 $MAX_RETRIES); do | ||
| RESPONSE=$(curl -sS --fail-with-body --connect-timeout 10 --max-time 60 -X POST "https://jules.googleapis.com/v1alpha/sessions" \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "X-Goog-Api-Key: ${JULES_API_KEY}" \ | ||
| -d @/tmp/jules-rebuild-analyze-payload.json) | ||
| CURL_EXIT=$? | ||
|
|
||
| if [ $CURL_EXIT -eq 0 ]; then | ||
| break | ||
| fi | ||
|
|
||
| ERROR_CODE=$(echo "$RESPONSE" | jq -r '.error.code // "0"') | ||
| ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // ""') | ||
|
|
||
| # Quota/rate-limit is non-retryable — exit immediately | ||
| if [ "$ERROR_CODE" = "429" ] || [ "$ERROR_CODE" = "403" ] || echo "$ERROR_MSG" | grep -qiE 'quota|limit|exceeded|capacity|daily'; then | ||
| echo "quota_exhausted=true" >> "$GITHUB_OUTPUT" | ||
| echo "⚠️ Session cap reached — no analysis session created." | ||
| exit 0 | ||
| fi | ||
| echo "Error: $RESPONSE"; exit 1 | ||
| fi | ||
|
|
||
| if [ $attempt -lt $MAX_RETRIES ]; then | ||
| echo "Attempt $attempt/$MAX_RETRIES failed (exit $CURL_EXIT), retrying in ${RETRY_DELAY}s..." | ||
| sleep $RETRY_DELAY | ||
| RETRY_DELAY=$((RETRY_DELAY * 2)) | ||
| else | ||
| echo "Error after $MAX_RETRIES attempts: $RESPONSE" | ||
| exit 1 | ||
| fi | ||
| done | ||
| set -e |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file excerpt =="
sed -n '140,220p' .github/workflows/jules-pr-rebuild.yml || true
echo "== retry/conflict related workflows =="
git ls-files '.github/workflows/*.yml' '.github/workflows/*.yaml' | xargs -r -n1 sh -c 'echo "--- $0"; rg -n "duplicate-session|duplicat|session cap|quota_exhausted|Analysis unavailable|for attempt|/jules.googleapis.com/v1alpha/sessions" "$0" || true'
echo "== diff stat/name-only =="
git diff --stat
git diff --name-onlyRepository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 10452
🌐 Web query:
Google Jules API create session idempotency /sessions duplicate session non-idempotent
💡 Result:
The Google Jules API's session creation endpoint (POST /v1alpha/sessions) is generally considered non-idempotent [1]. Each successful request to this endpoint creates a distinct, new session resource [2][3]. Key considerations regarding idempotency and session creation include: 1. No Built-in Idempotency Key: The official Jules API documentation [2][3][4][5] does not define or support a standard idempotency-key header (e.g., Request-Id or Idempotency-Key) for the session creation request. Consequently, repeating the same POST request will result in the creation of multiple, separate sessions, each with its own unique identifier [2][3][6]. 2. Potential for Duplication: Because the API does not recognize repeat requests as the same operation, client-side retry logic must be implemented cautiously [1]. If a network error or timeout occurs during the initial request, simply retrying the POST without verification may lead to duplicate sessions [1]. 3. Recommended Practice: To avoid duplicate sessions in production workflows, applications should check for existing sessions (e.g., using the ListSessions endpoint) [7][4] based on the project context or user prompt before triggering a new creation request, rather than relying on the API to deduplicate requests automatically. Technical note on payload structure: When interacting with the API, ensure your request payload is flat [8]. Unlike some Google APIs that follow the design guide recommendation to wrap resources in a parent object (e.g., { "session": {... } }), the Jules API expects the fields (prompt, sourceContext, etc.) at the root of the JSON body [8]. Providing a wrapped object will result in a 400 INVALID_ARGUMENT error [8].
Citations:
- 1: https://skill-repo.io/extensions/skill/sanjay3290/jules
- 2: https://developers.google.com/jules/api/reference/rest/v1alpha/sessions
- 3: https://jules.google/docs/api/reference/sessions/
- 4: https://developers.google.com/jules/api
- 5: https://jules.google/docs/api/reference/types/
- 6: https://jules.google/docs/api/reference/overview/
- 7: https://jules.google/docs/api/reference/
- 8: https://github.com/AVANT-ICONIC/jules-dispatch-cli/blob/main/docs/jules-api-notes.md
🌐 Web query:
"google.jules" GitHub v1alpha/sessions idempotency duplicate
💡 Result:
The Google Jules API (v1alpha/sessions) is an autonomous AI coding agent service [1][2]. According to the official API documentation for v1alpha/sessions, the create endpoint is used to initiate a new session [3]. There is no public evidence or specific documentation within the Google Jules ecosystem indicating that the v1alpha/sessions API natively supports Idempotency-Key headers or specific deduplication mechanisms to prevent duplicate session creation in the manner common to other major AI or cloud APIs [3][4]. While idempotency is a frequent requirement in distributed systems to handle network retries—often addressed by developers using idempotency keys [5][6][7]—official Google Jules documentation does not currently list this as a feature of the v1alpha sessions resource [3]. For developers interacting with Jules, the current standard interfaces are the web dashboard, the Jules SDK, and the Jules CLI (jules remote new) [8][9][4]. These tools abstract the underlying API calls, and users should rely on the documented CLI and SDK methods to manage session lifecycles [8][4]. If you are encountering issues with duplicate tasks, it is recommended to verify session status using jules remote list --session before triggering new operations [4].
Citations:
- 1: https://jules.google/
- 2: https://blog.google/innovation-and-ai/models-and-research/google-labs/jules/
- 3: https://developers.google.com/jules/api/reference/rest/v1alpha/sessions
- 4: https://jules.google/docs/cli/reference/
- 5: https://docs.pokee.ai/docs/idempotency
- 6: RetryingHttpClient can duplicate sessions events.send on a timed-out-but-landed request — request idempotency support anthropics/anthropic-sdk-java#370
- 7: ttoss/soat@b242655
- 8: https://github.com/google-labs-code/jules-sdk
- 9: https://developers.googleblog.com/en/meet-jules-tools-a-command-line-companion-for-googles-async-coding-agent/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== jules-session-create docs or references in repo =="
rg -n "sessions:create|create:|non-idempotent|Idempotency|idempotency|duplicate session|CreateSession|createSession|v1alpha/sessions" . || true
echo "== workflow retry idempotency key / existing session checks around create steps =="
python3 - <<'PY'
from pathlib import Path
import re
for p in Path(".github/workflows").glob("*.yml"):
txt=p.read_text()
if "jules.googleapis.com/v1alpha/sessions" in txt:
print(f"--- {p}")
for m in re.finditer(r"- name: .*session|for attempt in \$(seq|curl.*jules.googleapis.com|idempotency|Idempotency|session_name=|LIST|list.*session|duplicate", txt, flags=re.I):
# print up to 3 surrounding lines
start=max(0,m.start()-400); end=min(len(txt),m.end()+800)
snippet=txt[start:end]
out="\n".join(snippet.splitlines()[max(0,snippet.splitlines().index([l for l in snippet.splitlines() if 'curl' in l or 'jules.googleapis.com' in l or 'for attempt' in l].pop().strip())-3):min(len(snippet.splitlines()),5)])
print(out.strip() if not out else snippet)
break
PYRepository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 4585
Avoid retrying the non-idempotent Jules POST blindly
POST /v1alpha/sessions can create a new session on each successful request, and this retry loop will POST again after a failed attempt. Add a durable pre-POST guard/idemsotency marker, or otherwise avoid retrying session creation if the first POST may have already created a session.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-rebuild.yml around lines 174 - 206, The
session-creation retry loop around the Jules POST can duplicate sessions when a
request succeeds but its response is lost. Replace the blind retries in the `for
attempt` block with a durable pre-POST guard or supported idempotency mechanism,
ensuring the workflow does not issue another `POST /v1alpha/sessions` after the
first attempt may have created a session.
| MAX_RETRIES=3 | ||
| RETRY_DELAY=5 | ||
| for attempt in $(seq 1 $MAX_RETRIES); do | ||
| RESPONSE=$(curl -sS --fail-with-body --connect-timeout 10 --max-time 60 -X POST "https://jules.googleapis.com/v1alpha/sessions" \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "X-Goog-Api-Key: ${JULES_API_KEY}" \ | ||
| -d @/tmp/jules-review-payload.json) | ||
| CURL_EXIT=$? | ||
|
|
||
| if [ $CURL_EXIT -eq 0 ]; then | ||
| break | ||
| fi | ||
|
|
||
| if [ $CURL_EXIT -ne 0 ]; then | ||
| ERROR_CODE=$(echo "$RESPONSE" | jq -r '.error.code // "0"') | ||
| ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // ""') | ||
|
|
||
| # Quota/rate-limit is non-retryable — exit immediately | ||
| if [ "$ERROR_CODE" = "429" ] || [ "$ERROR_CODE" = "403" ] || echo "$ERROR_MSG" | grep -qiE 'quota|limit|exceeded|capacity|daily'; then | ||
| echo "quota_exhausted=true" >> "$GITHUB_OUTPUT" | ||
| echo "⚠️ Session cap reached — no session created." | ||
| exit 0 | ||
| fi | ||
| echo "Error: $RESPONSE" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ $attempt -lt $MAX_RETRIES ]; then | ||
| echo "Attempt $attempt/$MAX_RETRIES failed (exit $CURL_EXIT), retrying in ${RETRY_DELAY}s..." | ||
| sleep $RETRY_DELAY | ||
| RETRY_DELAY=$((RETRY_DELAY * 2)) | ||
| else | ||
| echo "Error after $MAX_RETRIES attempts: $RESPONSE" | ||
| exit 1 | ||
| fi | ||
| done | ||
| set -e |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate workflow =="
git ls-files | rg '(^|/)\.github/workflows/(jules-pr-review|walkthrough)\.yml$' || true
echo "== relevant workflow sections =="
wc -l .github/workflows/jules-pr-review.yml .github/workflows/walkthrough.yml 2>/dev/null || true
sed -n '280,355p' .github/workflows/jules-pr-review.yml
sed -n '350,395p' .github/workflows/walkthrough.yml
echo "== jules session creation references =="
rg -n "jules.googleapis.com/v1alpha/sessions|/sessions|max-attempts|max_retries|MAX_ATTEMPTS|TIMEOUT_MINUTES|timeout-minutes" .github/workflows/jules-pr-review.yml .github/workflows/walkthrough.yml 2>/dev/null || true
echo "== git diff stat/name-status if available =="
git diff --stat 2>/dev/null || true
git diff -- .github/workflows/jules-pr-review.yml .github/workflows/walkthrough.yml 2>/dev/null | sed -n '1,240p' || trueRepository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 3360
🌐 Web query:
Google Jules API create session POST sessions idempotent duplicate sessions
💡 Result:
The Jules API (v1alpha) sessions.create endpoint, accessed via a POST request to https://jules.googleapis.com/v1alpha/sessions, is not natively documented as idempotent [1][2][3]. In standard API design, POST requests for resource creation are typically non-idempotent because each successful call generates a new, unique session resource [1][4]. If you are concerned about duplicate sessions resulting from network retries or application logic errors, consider the following technical observations and best practices: 1. Absence of Idempotency Keys: The official Jules API documentation for the sessions.create method does not mention support for an idempotency-key header or request parameter (unlike some other Google Cloud services that use such headers to prevent duplicate resource creation) [1][2][5]. 2. Resource Creation Nature: The API creates a "continuous unit of work" (a session) [2][4]. Similar to other non-idempotent creation patterns, repeated calls to the same endpoint without unique identifiers would theoretically initiate multiple distinct session contexts, each with its own lifecycle [1][4]. 3. General Best Practices: If you must ensure that only one session is created for a specific task, you should implement application-side tracking. Before calling the creation endpoint, verify the state of existing sessions using the sessions.list method to see if a session for your target context (e.g., specific repository or task) already exists [1][2]. 4. Payload Structure: Note that the Jules API session creation payload is flat [6]. While some Google Cloud APIs expect a wrapped structure (e.g., {"session": {...}}), the Jules API rejects this as an INVALID_ARGUMENT [6]. For production-grade integrations, avoid automatic blind retries on POST requests to sessions.create. If a network error occurs, first list existing sessions to confirm whether the previous attempt succeeded before deciding to retry the creation request [1][6].
Citations:
- 1: https://developers.google.com/jules/api/reference/rest/v1alpha/sessions
- 2: https://developers.google.com/jules/api
- 3: https://developers.google.com/jules/api/reference/rest
- 4: https://developers.googleblog.com/en/level-up-your-dev-game-the-jules-api-is-here/
- 5: https://cloud.google.com/discover/idempotency
- 6: https://github.com/AVANT-ICONIC/jules-dispatch-cli/blob/main/docs/jules-api-notes.md
🌐 Web query:
Google Jules API error messages failed HTTP retry 5xx curl exit code curl --fail-with-body
💡 Result:
When interacting with the Google Jules API via curl, encountered 5xx server errors are generally considered transient and eligible for automatic retries by curl if configured correctly [1]. If you are using curl --retry to handle these errors, it is important to note how this interacts with failure flags [2][1]: 1. HTTP 5xx Behavior: By default, curl does not consider HTTP 5xx response codes to be a failure of the transfer itself [3]. Consequently, curl will not trigger its built-in retry mechanism unless you explicitly instruct it to treat these status codes as failures [3][1]. 2. Using --fail-with-body: The --fail-with-body option is the recommended way to ensure that curl treats HTTP 400+ responses as errors (returning exit code 22) while still outputting the response body (which may contain helpful error details from the Jules API) [4][2]. 3. Interaction with --retry: In older versions of curl, combining flags could sometimes interfere with retry behavior [5][2]. However, in modern versions, curl --retry <count> --fail-with-body <URL> is the standard pattern to ensure that the request retries upon encountering a 5xx error until the retry budget is exhausted or the request succeeds [2]. If you continue to encounter issues: - Verify your curl version: Use curl --version to ensure you are on a recent release, as older versions had known bugs where --fail-with-body prevented retries [5][2]. - Check for Rate Limiting: The Jules API may return errors when rate limits are exceeded [6][7]. While standard rate limits should return a 429 status code, ensure your implementation handles these explicitly [6]. - Review Jules Activity Logs: Jules surfaces detailed error information in its UI activity feed, which can help determine if a 5xx error is a transient server-side issue or a configuration error in your request [8]. For automated or programmatic workflows, ensure that your client code handles the exit code 22 appropriately, as this is the standard curl exit code when --fail-with-body is triggered [4][3].
Citations:
- 1: https://www.simplified.guide/curl/retry-configure-transient-error
- 2: Document that --fail undoes --retry curl/curl#20771
- 3: https://everything.curl.dev/http/response.html
- 4: https://curl.se/docs/manpage.html
- 5: --retry does not retry when combined with --fail-with-body curl/curl#9078
- 6: High concurrency (10+ parallel sessions) fails silently - rate limits undocumented google-labs-code/jules-sdk#20
- 7: https://jules.google/docs/api/reference/authentication/
- 8: https://jules.google/docs/errors/
Make the Jules session retry idempotent before retrying POST.
This retry loop can submit POST /jules.googleapis.com/v1alpha/sessions multiple times. Because session creation is a non-idempotent resource operation and this loop does not distinguish a server response failure whose request may have partially created a session, a retry can create an extra review session. List the target PR session first before retrying, or otherwise gate retries to known non-server-side failures that did not proceed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-review.yml around lines 307 - 339, Make the retry
logic around the session-creation POST idempotent by preventing blind retries
after failures that may have reached the server. Before retrying, list the
target PR’s existing Jules sessions and reuse or stop when a matching session is
found; otherwise restrict retries to clearly client-side failures known not to
have processed the request. Preserve the existing quota detection and final
failure behavior in the retry loop.
| run: | | ||
| SESSION_RESOURCE="${SESSION_NAME:-sessions/${SESSION_ID}}" | ||
| MAX_ATTEMPTS=60 | ||
| MAX_ATTEMPTS=120 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Poll budget now equals the job timeout, leaving zero slack.
MAX_ATTEMPTS=120 × 15s sleep = 1800s = 30 min, exactly the job's timeout-minutes: 30 (line 31) — before accounting for checkout, prompt build, the new create-session retry loop, artifact upload, review posting, and the feedback-summary step. The job will very likely hit the GitHub Actions timeout mid-poll. See consolidated comment (shared with jules-pr-walkthrough.yml).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-review.yml at line 376, Reduce MAX_ATTEMPTS in
the polling logic so the 15-second polling budget leaves sufficient slack within
the job’s 30-minute timeout for setup, retries, artifact upload, review posting,
and feedback-summary processing. Apply the same adjusted budget to the
corresponding workflow if it shares this polling configuration.
|
|
||
| // Save posted findings for cross-session dedup | ||
| const postedFindings = {}; | ||
| for (const c of comments) { | ||
| postedFindings[`${c.path}:${c.line}`] = true; | ||
| } | ||
| // Update or create the findings tracking comment | ||
| const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`; | ||
| if (prevFindingsComment) { | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: prevFindingsComment.id, | ||
| body: findingsBody, | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: prNumber, | ||
| body: findingsBody, | ||
| }); | ||
| } | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Cross-session dedup state is overwritten, not merged — the feature loses its own history on every run.
postedFindings is built only from comments (this run's newly-posted findings), then this object fully replaces the <!-- jules-posted-findings --> tracking comment via updateComment/createComment. Previously-recorded keys from prevFindingsComment are never merged in, so:
- Every save drops all prior findings that weren't re-posted this run.
- A run with zero new findings (everything already
seen, or nothing to report) overwrites the tracking comment with an empty{}, erasing the entire dedup history.
The very next session will then treat previously-tracked findings as unseen again, defeating PR objective #6 ("deduplicate findings across sessions using a state file").
🐛 Proposed fix — merge previous findings before saving
// Save posted findings for cross-session dedup
const postedFindings = {};
+ if (prevFindingsComment) {
+ try {
+ const prevFindings = JSON.parse(prevFindingsComment.body.replace(/.*<!-- jules-posted-findings -->\n?/, '').trim());
+ Object.assign(postedFindings, prevFindings);
+ } catch (e) { /* ignore parse errors */ }
+ }
for (const c of comments) {
postedFindings[`${c.path}:${c.line}`] = true;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Save posted findings for cross-session dedup | |
| const postedFindings = {}; | |
| for (const c of comments) { | |
| postedFindings[`${c.path}:${c.line}`] = true; | |
| } | |
| // Update or create the findings tracking comment | |
| const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`; | |
| if (prevFindingsComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: prevFindingsComment.id, | |
| body: findingsBody, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: findingsBody, | |
| }); | |
| } | |
| // Save posted findings for cross-session dedup | |
| const postedFindings = {}; | |
| if (prevFindingsComment) { | |
| try { | |
| const prevFindings = JSON.parse(prevFindingsComment.body.replace(/.*<!-- jules-posted-findings -->\n?/, '').trim()); | |
| Object.assign(postedFindings, prevFindings); | |
| } catch (e) { /* ignore parse errors */ } | |
| } | |
| for (const c of comments) { | |
| postedFindings[`${c.path}:${c.line}`] = true; | |
| } | |
| // Update or create the findings tracking comment | |
| const findingsBody = `<!-- jules-posted-findings -->\n${JSON.stringify(postedFindings)}`; | |
| if (prevFindingsComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: prevFindingsComment.id, | |
| body: findingsBody, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: findingsBody, | |
| }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-review.yml around lines 563 - 586, Update the
postedFindings construction in the cross-session dedup save flow to parse and
preserve the existing findings from prevFindingsComment before adding the
current run’s comments. Merge current keys into that prior state, including when
comments is empty, then serialize the merged object for the existing
updateComment/createComment calls so previously tracked findings are never
discarded.
| MAX_RETRIES=3 | ||
| RETRY_DELAY=5 | ||
| for attempt in $(seq 1 $MAX_RETRIES); do | ||
| RESPONSE=$(curl -sS --fail-with-body --connect-timeout 10 --max-time 60 -X POST "https://jules.googleapis.com/v1alpha/sessions" \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "X-Goog-Api-Key: ${JULES_API_KEY}" \ | ||
| -d @/tmp/jules-walkthrough-payload.json) | ||
| CURL_EXIT=$? | ||
|
|
||
| if [ $CURL_EXIT -eq 0 ]; then | ||
| break | ||
| fi | ||
|
|
||
| if [ $CURL_EXIT -ne 0 ]; then | ||
| ERROR_CODE=$(echo "$RESPONSE" | jq -r '.error.code // "0"') | ||
| ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // ""') | ||
|
|
||
| # Quota/rate-limit is non-retryable — exit immediately | ||
| if [ "$ERROR_CODE" = "429" ] || [ "$ERROR_CODE" = "403" ] || echo "$ERROR_MSG" | grep -qiE 'quota|limit|exceeded|capacity|daily'; then | ||
| echo "quota_exhausted=true" >> "$GITHUB_OUTPUT" | ||
| echo "⚠️ Session cap reached — no session created." | ||
| exit 0 | ||
| fi | ||
| echo "Error: $RESPONSE" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ $attempt -lt $MAX_RETRIES ]; then | ||
| echo "Attempt $attempt/$MAX_RETRIES failed (exit $CURL_EXIT), retrying in ${RETRY_DELAY}s..." | ||
| sleep $RETRY_DELAY | ||
| RETRY_DELAY=$((RETRY_DELAY * 2)) | ||
| else | ||
| echo "Error after $MAX_RETRIES attempts: $RESPONSE" | ||
| exit 1 | ||
| fi | ||
| done | ||
| set -e |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Workflow file excerpt:"
sed -n '180,245p' .github/workflows/jules-pr-walkthrough.yml 2>/dev/null || true
echo
echo "Session creation payload/schema references:"
sed -n '1,220p' .github/workflows/jules-pr-walkthrough.yml 2>/dev/null | rg -n "jules-walkthrough-payload|sessions|idempotency|requestId|displayName|clientSession|JULES_API_KEY|error.code|quota|429|403|curl" -C 3 || trueRepository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 5035
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Workflow diff/stat context:"
git diff -- .github/workflows/jules-pr-walkthrough.yml 2>/dev/null | sed -n '1,220p' || true
echo
echo "Search for idempotency headers/keys in workflows:"
rg -n "idempotency|idempotent|requestId|x-goog-request-params|client_session|displayName|createSession|/sessions" .github/workflows || trueRepository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 2328
🌐 Web query:
Google Jules API create sessions idempotency header clientSession request idempotency
💡 Result:
The Jules API, as documented in Google's official developer resources, does not explicitly list a specific idempotency header (such as Request-ID or similar) in its v1alpha REST documentation [1][2][3]. Current API usage for creating sessions utilizes a POST request to the https://jules.googleapis.com/v1alpha/sessions endpoint [1][4]. Authentication is handled by passing an API key in the X-Goog-Api-Key header [1][5]. There is no mention in the public documentation of a clientSession request parameter or specific idempotency header requirements for session creation [1][3]. While some Google Cloud APIs use a requestId parameter to ensure idempotency for resource creation [6][7], this pattern is not currently documented as part of the Jules API interface [3]. If you are working with client-side sessions in libraries (like aiohttp.ClientSession), those refer to internal HTTP connection management rather than API-level request idempotency [8][9]. If you are encountering issues with duplicate session creation, it is recommended to manage idempotency at the application logic level or check for updates to the Jules API documentation, as it is marked as an early look/v1alpha release [10][4].
Citations:
- 1: https://developers.google.com/jules/api
- 2: https://developers.google.com/jules/api/reference/rest
- 3: https://developers.google.com/jules/api/reference/rest/v1alpha/sessions
- 4: https://developers.googleblog.com/en/level-up-your-dev-game-the-jules-api-is-here/
- 5: https://dr-arsanjani.medium.com/dive-into-the-technical-details-of-the-new-google-jules-api-672182748059
- 6: https://googleapis.dev/java/google-api-services-drive/v2-rev20210524-1.31.0/com/google/api/services/drive/Drive.Drives.Insert.html
- 7: https://discuss.google.dev/t/critical-bug-report-transaction-requestid-duplicated-server-fails-idempotency-check-on-10-days-apart-syncs-causing-data-overwrite-zombie-edit/322591/6
- 8: https://googleapis.dev/python/google-auth/2.28.2/reference/google.auth.transport._aiohttp_requests.html
- 9: https://github.com/googleapis/python-genai/blob/main/google/genai/_api_client.py
- 10: https://blog.google/innovation-and-ai/models-and-research/google-labs/jules-tools-jules-api/
Make the walkthrough session creation retry-safe or remove the retry
This retry loop resubmits the same POST /v1alpha/sessions request without a stable predicate/idempotency key, so transient failures can create duplicate walkthrough sessions for the same PR/payload. Add an API-supported idempotency/x-goog-request-params key before retrying, or replace the retry with a deterministic fail/defer path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-walkthrough.yml around lines 203 - 235, Make the
session-creation flow around the retry loop safe for repeated POST attempts by
supplying an API-supported stable idempotency or request-parameters key derived
from the same PR/payload, or remove retries and use a deterministic
failure/defer path. Ensure repeated attempts cannot create duplicate walkthrough
sessions.
| run: | | ||
| SESSION_RESOURCE="${SESSION_NAME:-sessions/${SESSION_ID}}" | ||
| MAX_ATTEMPTS=60 | ||
| MAX_ATTEMPTS=120 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Poll budget now equals the job timeout, leaving zero slack.
MAX_ATTEMPTS=120 × 15s sleep = 1800s = 30 min, exactly the job's timeout-minutes: 30 (line 20) — before accounting for checkout, payload build, the new create-session retry loop (up to ~3 min), artifact upload, or comment posting. The job will very likely be killed by the GitHub Actions timeout before the poll loop can even reach 120 attempts, silently negating this change. See consolidated comment (shared with jules-pr-review.yml).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/jules-pr-walkthrough.yml at line 273, Reduce MAX_ATTEMPTS
in the polling configuration so the 15-second polling budget leaves sufficient
slack within the job’s 30-minute timeout for setup, session retries, artifact
upload, and comment posting. Apply the same adjusted budget to the corresponding
polling configuration in the related workflow to keep both jobs consistent.
🤖 Recovered Review Content (Session
|



Applies 7 fixes to Jules review workflows