opencode icon indicating copy to clipboard operation
opencode copied to clipboard

github: support schedule events

Open elithrar opened this issue 2 months ago • 15 comments

note: draft for now as I want to make sure we're happy with this / think through it over the weekend.

Adds support for schedule events in the GitHub Action, allowing users to run OpenCode on a cron schedule.

  • add schedule to allowed event types
  • skip /opencode keyword check for schedule events (no comment to parse)
  • skip creating issue comment for schedule events (no issue/PR context)
  • prompt input is required for schedule events
  • branch naming: opencode/scheduled-{6-random-hex-chars}-{timestamp}
  • omit co-authored-by on commits (schedule operates as the repo)
  • add "Supported Events" section to docs with table and example workflow

Users can now create scheduled workflows that invoke OpenCode with a prompt to perform automated tasks like code reviews, reports, or maintenance. OpenCode can still open PRs/issues and call tools (webhooks, etc.) as needed.

elithrar avatar Dec 19 '25 20:12 elithrar

I also tried to keep this pretty compact — most of the scaffolding was there, but b/c scheduled events don't get comment bodies + can't work on branches needed to make sure that was covered.

elithrar avatar Dec 19 '25 20:12 elithrar

call Stack: schedule vs issue_comment

┌─────────────────────────────────────────────────────────────────────────────────┐
│                              GITHUB ACTION ENTRY                                │
│                       github/action.yml → opencode github run                   │
│                      packages/opencode/src/cli/cmd/github.ts                    │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│              context.eventName check (lines 391-398)                            │
│  Allowed: "issue_comment" | "pull_request_review_comment" | "schedule"          │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                         isScheduleEvent (line 390)                              │
│                   const isScheduleEvent = context.eventName === "schedule"      │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                              getUserPrompt()                                    │
├─────────────────────────────────────┬───────────────────────────────────────────┤
│           SCHEDULE                  │              ISSUE_COMMENT                │
├─────────────────────────────────────┼───────────────────────────────────────────┤
│  prompt = process.env["PROMPT"]     │  prompt = payload.comment.body            │
│  ├─ ✗ FAIL: !prompt → throw         │  + image processing                       │
│  └─ ✓ PASS: return { userPrompt }   │  └─ returns { userPrompt, promptFiles }   │
└─────────────────────────────────────┴───────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                              configureGit()                                     │
│                          (same for both events)                                 │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│            if (!isScheduleEvent) { assertPermissions(); addReaction() }         │
├─────────────────────────────────────┬───────────────────────────────────────────┤
│           SCHEDULE                  │              ISSUE_COMMENT                │
├─────────────────────────────────────┼───────────────────────────────────────────┤
│  skipped (no actor/comment)         │  checks collaborator permission           │
│                                     │  adds 👀 reaction to comment              │
└─────────────────────────────────────┴───────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                     Session.create() + subscribeSessionEvents()                 │
│                              (same for both)                                    │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                           MAIN EVENT BRANCHING                                  │
│                     if (isScheduleEvent) { ... }                                │
│                     else if (isPullRequest) { ... }                             │
│                     else { /* issue */ }                                        │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        │
        ┌───────────────────────────────┴───────────────────────────────┐
        ▼                                                               ▼
┌───────────────────────────────────┐       ┌───────────────────────────────────┐
│           SCHEDULE                │       │         ISSUE_COMMENT             │
├───────────────────────────────────┤       ├───────────────────────────────────┤
│                                   │       │                                   │
│  checkoutNewBranch("schedule")    │       │  checkoutNewBranch("issue")       │
│  → opencode/scheduled-{hex}-{ts}  │       │  → opencode/issue{N}-{ts}         │
│                                   │       │                                   │
│  chat(userPrompt, [])             │       │  fetchIssue()                     │
│                                   │       │  buildPromptDataForIssue()        │
│                                   │       │  chat(userPrompt + data, files)   │
│                                   │       │                                   │
├───────────────────────────────────┤       ├───────────────────────────────────┤
│  if (dirty) {                     │       │  if (dirty) {                     │
│    summarize()                    │       │    summarize()                    │
│    pushToNewBranch(isSchedule=✓)  │       │    pushToNewBranch(isSchedule=✗)  │
│      └─ NO co-author line         │       │      └─ WITH co-author line       │
│    createPR()  ✅                 │       │    createPR()                     │
│    console.log(PR #)              │       │    createComment(PR #)            │
│  } else {                         │       │    removeReaction()               │
│    console.log(response)          │       │  } else {                         │
│  }                                │       │    createComment(response)        │
│                                   │       │    removeReaction()               │
│  // NO createComment              │       │  }                                │
│  // NO removeReaction             │       │                                   │
└───────────────────────────────────┘       └───────────────────────────────────┘

Failure Modes

Stage Schedule Event Issue Comment Event
eventName check ✓ passes ✓ passes
getUserPrompt ✗ fails if PROMPT env missing ✗ fails if no /oc or /opencode in body
assertPermissions skipped ✓ typically passes (human actor)
addReaction skipped ✓ adds 👀 to comment
chat ✗ fails on LLM/tool errors ✗ fails on LLM/tool errors
pushToNewBranch ✗ fails on git push errors ✗ fails on git push errors
createPR ✅ can create PRs ✅ can create PRs
createComment skipped ✗ fails on API error
removeReaction skipped ✓ removes 👀 from comment

Key Differences

Aspect Schedule Issue/PR Comment
Prompt source PROMPT env var (required) Comment body
Branch name opencode/scheduled-{6-char-hex}-{timestamp} opencode/issue{N}-{timestamp}
Co-authored-by Omitted (schedule operates as repo) Included (credits human actor)
Output Console logs GitHub comment
Reactions None 👀 added/removed
Permission check Skipped Required

elithrar avatar Dec 19 '25 21:12 elithrar

User elithrar does not have write permissions

github run

opencode-agent[bot] avatar Dec 19 '25 21:12 opencode-agent[bot]

OK, I think I’m happy with this after staring at it again last night.

elithrar avatar Dec 20 '25 15:12 elithrar

/review

rekram1-node avatar Dec 20 '25 18:12 rekram1-node

lgtm

github-actions[bot] avatar Dec 20 '25 18:12 github-actions[bot]

oh @elithrar I should really clean this up but rn github/index.ts does nothing, rn all the github workflow stuff is in ...../cmd/github.ts

At one point we meant to move it to be fully contained in the action and that's why github/index.ts exists but we never finished the migration and now it's just sitting there, so youll probably want to make changes to cmd/github.ts instead

Unless this changed recently ig, but last I checked that was the case still...

rekram1-node avatar Dec 20 '25 18:12 rekram1-node

Yeah appears to still use github cmd: https://github.com/sst/opencode/blob/ad6a5e6157ea728d3d2cb29297d6bbb5f79dc6bc/github/action.yml#L66

rekram1-node avatar Dec 20 '25 18:12 rekram1-node

ah duh — all the other PRs I've made have been against the CLI imp. Fixing.

elithrar avatar Dec 20 '25 18:12 elithrar

fixed + upgraded diagram to show a couple of tweaks

elithrar avatar Dec 20 '25 19:12 elithrar

/review

rekram1-node avatar Dec 20 '25 21:12 rekram1-node

Updated this and re-reviewed.

If we add more events I want to re-think how we can make the active user event vs repo driven event control flow easier to follow.

On Sat, Dec 20, 2025 at 16:38 github-actions[bot] @.***> wrote:

@.**[bot] commented on this pull request.

In packages/opencode/src/cli/cmd/github.ts https://github.com/sst/opencode/pull/5810#discussion_r2637382503:

       reaction_id: eyesReaction.id,
     })
   }

   async function createComment(body: string) {
  •    // Only called for non-schedule events, so issueId is defined
       console.log("Creating comment...")
       return await octoRest.rest.issues.createComment({
    

Potential bug: The comment says "Only called for non-schedule events", but the catch block in the main try/catch (around line 536-542) calls createComment() unconditionally on error, which would fail for schedule events since issueId is undefined.

You may want to wrap the catch block calls to createComment() and removeReaction() with if (!isScheduleEvent) to prevent runtime errors during scheduled runs.

— Reply to this email directly, view it on GitHub https://github.com/sst/opencode/pull/5810#pullrequestreview-3601326780, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAEQ4GGMIXKM4E5UWFXL5T4CW6XDAVCNFSM6AAAAACPSMSIDCVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZTMMBRGMZDMNZYGA . You are receiving this because you were mentioned.Message ID: @.***>

elithrar avatar Dec 20 '25 22:12 elithrar

/review

rekram1-node avatar Dec 21 '25 02:12 rekram1-node

Code review complete. Found one potential bug (commented inline). Otherwise the code follows the style guide - uses early returns where appropriate, keeps functions together, and doesn't introduce unnecessary else statements or excessive nesting. The changes look good overall.

github-actions[bot] avatar Dec 21 '25 03:12 github-actions[bot]

Reviewed and addressed the following for schedule event handling:

  • Skip assertPermissions for schedule events (avoids github-actions[bot] actor failure)
  • Fix summarize() fallback to avoid accessing undefined payload for schedule events
  • Verified all issue.title, issue.body, pr.title, pr.body callsites are guarded

elithrar avatar Dec 21 '25 13:12 elithrar