Watch Session PRs #120
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Watch Session PRs | |
| on: | |
| schedule: | |
| - cron: '17 0 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| issues: write | |
| jobs: | |
| watch-codex-fork: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Check Codex fork PR and update issue | |
| uses: actions/github-script@v7 | |
| env: | |
| UPSTREAM_OWNER: openai | |
| UPSTREAM_REPO: codex | |
| UPSTREAM_PR_NUMBER: '13537' | |
| TARGET_ISSUE_NUMBER: '7' | |
| COMMENT_MARKER: '<!-- codex-fork-pr-13537-status -->' | |
| READY_LABEL: '[ready]' | |
| STOP_LABEL: '[stop]' | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const owner = process.env.UPSTREAM_OWNER; | |
| const repo = process.env.UPSTREAM_REPO; | |
| const pull_number = Number(process.env.UPSTREAM_PR_NUMBER); | |
| const issue_number = Number(process.env.TARGET_ISSUE_NUMBER); | |
| const marker = process.env.COMMENT_MARKER; | |
| const readyLabel = process.env.READY_LABEL; | |
| const stopLabel = process.env.STOP_LABEL; | |
| const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }); | |
| const desiredLabel = pr.merged_at ? readyLabel : stopLabel; | |
| const staleLabel = pr.merged_at ? stopLabel : readyLabel; | |
| const { data: issue } = await github.rest.issues.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| }); | |
| if (!issue.labels.some((label) => label.name === desiredLabel)) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| labels: [desiredLabel], | |
| }); | |
| core.info(`Added label ${desiredLabel} to issue #${issue_number}.`); | |
| } | |
| if (issue.labels.some((label) => label.name === staleLabel)) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| name: staleLabel, | |
| }); | |
| core.info(`Removed label ${staleLabel} from issue #${issue_number}.`); | |
| } catch (error) { | |
| core.warning(`Failed to remove label ${staleLabel}: ${error.message}`); | |
| } | |
| } | |
| core.summary | |
| .addHeading('Codex PR status') | |
| .addTable([ | |
| [ | |
| { data: 'Field', header: true }, | |
| { data: 'Value', header: true }, | |
| ], | |
| ['PR', `${owner}/${repo}#${pr.number}`], | |
| ['Title', pr.title], | |
| ['State', pr.state], | |
| ['Merged', pr.merged_at ? 'yes' : 'no'], | |
| ['Merged at', pr.merged_at ?? 'not merged'], | |
| ['Updated at', pr.updated_at], | |
| ['URL', pr.html_url], | |
| ]); | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| per_page: 100, | |
| }); | |
| const existingComment = comments.find((comment) => comment.body?.includes(marker)); | |
| const body = [ | |
| marker, | |
| 'upstream PR `openai/codex#13537` status watcher', | |
| '', | |
| `- PR: ${pr.html_url}`, | |
| `- State: ${pr.state}`, | |
| `- Merged: ${pr.merged_at ? 'yes' : 'no'}`, | |
| `- Merged at: ${pr.merged_at ?? 'not merged'}`, | |
| `- Updated at: ${pr.updated_at}`, | |
| '', | |
| pr.merged_at | |
| ? 'This PR has been merged. `codex exec --fork <SESSION_ID> [PROMPT]` should become available once the change lands in the installed Codex version.' | |
| : 'This PR is not merged yet. Non-interactive forking is still not available in released Codex builds.', | |
| 'A new session ID is expected when using `--fork`; `resume` itself continues the same session.', | |
| ].join('\n'); | |
| if (existingComment?.body === body) { | |
| core.info(`Issue #${issue_number} status comment is already up to date.`); | |
| await core.summary.addRaw(`No update needed: ${existingComment.html_url}\n`).write(); | |
| return; | |
| } | |
| if (existingComment) { | |
| const { data: comment } = await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body, | |
| }); | |
| core.info(`Updated issue #${issue_number}: ${comment.html_url}`); | |
| await core.summary.addRaw(`Updated status comment: ${comment.html_url}\n`).write(); | |
| return; | |
| } | |
| const { data: comment } = await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| body, | |
| }); | |
| core.info(`Created issue #${issue_number} status comment: ${comment.html_url}`); | |
| await core.summary.addRaw(`Created status comment: ${comment.html_url}\n`).write(); | |
| watch-gemini-session-work: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Check Gemini session PRs and update issue | |
| uses: actions/github-script@v7 | |
| env: | |
| UPSTREAM_OWNER: google-gemini | |
| UPSTREAM_REPO: gemini-cli | |
| TARGET_ISSUE_NUMBER: '16' | |
| COMMENT_MARKER: '<!-- gemini-session-pr-status -->' | |
| WATCH_PR_NUMBERS: '17921,19629,19994' | |
| READY_PR_NUMBERS: '17921,19629' | |
| READY_LABEL: '[ready]' | |
| STOP_LABEL: '[stop]' | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const owner = process.env.UPSTREAM_OWNER; | |
| const repo = process.env.UPSTREAM_REPO; | |
| const issue_number = Number(process.env.TARGET_ISSUE_NUMBER); | |
| const marker = process.env.COMMENT_MARKER; | |
| const pullNumbers = process.env.WATCH_PR_NUMBERS.split(',').map((value) => Number(value.trim())); | |
| const readyPullNumbers = process.env.READY_PR_NUMBERS.split(',').map((value) => Number(value.trim())); | |
| const readyLabel = process.env.READY_LABEL; | |
| const stopLabel = process.env.STOP_LABEL; | |
| const pulls = await Promise.all( | |
| pullNumbers.map(async (pull_number) => { | |
| const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }); | |
| return pr; | |
| }), | |
| ); | |
| const isReady = pulls.some((pr) => readyPullNumbers.includes(pr.number) && Boolean(pr.merged_at)); | |
| const desiredLabel = isReady ? readyLabel : stopLabel; | |
| const staleLabel = isReady ? stopLabel : readyLabel; | |
| const { data: issue } = await github.rest.issues.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| }); | |
| if (!issue.labels.some((label) => label.name === desiredLabel)) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| labels: [desiredLabel], | |
| }); | |
| core.info(`Added label ${desiredLabel} to issue #${issue_number}.`); | |
| } | |
| if (issue.labels.some((label) => label.name === staleLabel)) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| name: staleLabel, | |
| }); | |
| core.info(`Removed label ${staleLabel} from issue #${issue_number}.`); | |
| } catch (error) { | |
| core.warning(`Failed to remove label ${staleLabel}: ${error.message}`); | |
| } | |
| } | |
| const lines = pulls.flatMap((pr) => [ | |
| `- \`${owner}/${repo}#${pr.number}\` ${pr.title}`, | |
| ` - State: ${pr.state}`, | |
| ` - Merged: ${pr.merged_at ? 'yes' : 'no'}`, | |
| ` - Merged at: ${pr.merged_at ?? 'not merged'}`, | |
| ` - Updated at: ${pr.updated_at}`, | |
| ` - URL: ${pr.html_url}`, | |
| ]); | |
| core.summary | |
| .addHeading('Gemini PR status') | |
| .addTable([ | |
| [ | |
| { data: 'PR', header: true }, | |
| { data: 'State', header: true }, | |
| { data: 'Merged', header: true }, | |
| { data: 'Updated at', header: true }, | |
| ], | |
| ...pulls.map((pr) => [ | |
| `${owner}/${repo}#${pr.number}`, | |
| pr.state, | |
| pr.merged_at ? 'yes' : 'no', | |
| pr.updated_at, | |
| ]), | |
| ]); | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| per_page: 100, | |
| }); | |
| const existingComment = comments.find((comment) => comment.body?.includes(marker)); | |
| const body = [ | |
| marker, | |
| 'upstream Gemini CLI session-related PR status watcher', | |
| '', | |
| 'Tracked PRs:', | |
| ...lines, | |
| '', | |
| 'Interpretation:', | |
| '- `#19994` is a fix for resumed session ID stats mismatch, not a fork/new-session feature.', | |
| '- `#17921` is the `/new` command proposal for starting a fresh session while preserving the previous one.', | |
| '- `#19629` is the `--session-id` proposal for explicit session UUID control.', | |
| '- Even if these land, that still does not automatically imply Codex-style headless fork semantics from `--resume` alone.', | |
| ].join('\n'); | |
| if (existingComment?.body === body) { | |
| core.info(`Issue #${issue_number} Gemini status comment is already up to date.`); | |
| await core.summary.addRaw(`No update needed: ${existingComment.html_url}\n`).write(); | |
| return; | |
| } | |
| if (existingComment) { | |
| const { data: comment } = await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body, | |
| }); | |
| core.info(`Updated issue #${issue_number}: ${comment.html_url}`); | |
| await core.summary.addRaw(`Updated status comment: ${comment.html_url}\n`).write(); | |
| return; | |
| } | |
| const { data: comment } = await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| body, | |
| }); | |
| core.info(`Created issue #${issue_number} status comment: ${comment.html_url}`); | |
| await core.summary.addRaw(`Created status comment: ${comment.html_url}\n`).write(); |