Skip to content

feat(dashboard): update tickets/ from GitHub without re-importing#1222

Merged
suleimansh merged 2 commits into
mainfrom
suleimansh/feat/1208-update-tickets
Jul 26, 2026
Merged

feat(dashboard): update tickets/ from GitHub without re-importing#1222
suleimansh merged 2 commits into
mainfrom
suleimansh/feat/1208-update-tickets

Conversation

@suleimansh

Copy link
Copy Markdown
Member

Closes #1208

Answering the question in the OP first

Yes, the API has it. Two endpoints, and the update uses both:

  • gh issue list --state all --search "updated:>=<lastImportedAt>" for issues touched since the stamp. updated, not created, so an old issue that was edited or newly commented comes back too.
  • gh api --paginate "repos/{owner}/{repo}/issues/comments?since=<lastImportedAt>" for the discussion, which issues?since= does not return bodies for.

What

The tickets view gets Update from GitHub once there is something to update, next to a line saying when tickets/ last caught up. The empty state keeps its Import tickets from GitHub button: same work, but "update" is a strange word for filling a directory that has never been filled.

Its own preset (update-tickets), not a flag on the import, because the two ask for different work. The import fills an empty directory. This one reconciles a full one:

  • an issue with no ticket gets one
  • an issue that has one has it edited in place, keeping its .spike.md and .plan.md (those are our work, not GitHub's, and re-importing must not discard them)
  • a closed issue's ticket is removed, with its spike and plan
  • new comments are folded in only where they change what the work is, rather than pasted

The stamp

tickets/meta.json holds {"lastImportedAt": "<ISO 8601>"}, exactly as the OP suggested.

Written by the agent, not by the framework, which is the part worth arguing about. The file then travels in the same commit as the tickets it describes, through the same merge, so a run whose work never landed cannot leave behind a stamp claiming those issues were imported. A framework-written stamp would have had to guess whether the agent's import succeeded.

The timestamp is taken before the fetch, not after. An issue edited while the update runs is then picked up by the next update instead of falling into the gap between the fetch and the stamp. Re-importing one issue is cheap; silently missing one is not.

The importer writes the stamp too, so the first update has somewhere to resume from instead of re-reading the whole repo.

Framework-side, readTicketsMeta answers {} for every way the file can be unusable (absent, unreadable, not JSON, not an object, no key, wrong type, unparseable date). It is written by an agent and rendered into the UI, so "we do not know when this last synced" is the only safe failure, and it is pinned by a test with a case per shape.

Verified in the real UI

Isolated HOME, a scratch git repo with one ticket, the worktree build on a spare port, claude stripped from PATH:

The prompt sent is pinned as presets.updateTickets.render() verbatim in the panel test, which is where the drift risk actually is.

Tests

the-framework 1371 pass / 1 fail, framework-dashboard 512 pass / 0 fail, full monorepo typecheck 22/22.

The one failure is the same #1122 re-home flake (#1165) that hit #1220, on files this branch does not touch.

Three catalog invariants moved rather than broke, and they are worth a look in review since each states a rule:

  • the pinned run-kind name list gains update-tickets
  • LAUNCHER_PRESETS gains it, so it is offered from the launcher as well as the panel
  • "one preset, and only one, always opens a session of its own" becomes the pair. Updating is repo work for the same reason importing is: it has nothing to do with whichever session the user happened to be reading.

Importing was a one-shot: the only way to pick up issues filed since was
to run it again over a directory that already had tickets in it. The
tickets view now offers "Update from GitHub" once there is something to
update, and says when tickets/ last caught up.

The update resumes rather than refills. It reads `lastImportedAt` from
tickets/meta.json and asks GitHub only for what changed since, both
issues and comments, then reconciles: an existing ticket is edited in
place, the .spike.md and .plan.md written against it are kept, and a
closed issue's ticket goes. With no stamp on record it falls back to
bringing every open issue across, so a repo imported before this existed
still works.

The stamp is written by the agent, in the same commit as the tickets it
describes, so a run whose work never landed cannot leave behind a claim
that those issues were imported. The importer writes it too, which is
what gives the first update somewhere to resume from.

The timestamp recorded is the one taken before the fetch, not after: an
issue edited mid-run is then picked up by the next update rather than
falling into the gap.

Closes #1208
@suleimansh

Copy link
Copy Markdown
Member Author

The red drift check is the pre-existing one, not this branch. The workflow runs here only because the PR touches prompts/**; presets are not drift-guarded, system_prompt.md is, and this branch does not change it.

first difference at line 8:
  issue:  "ADD_ANALYSIS_ENTRY: Add entry to the ANLYSIS_RESULT.md list"
  repo:   "ADD_ANALYSIS_ENTRY: Add entry to the ANALYSIS_RESULT.md list"

The repo is right and issue #326 is stale by one character (ANLYSIS -> ANALYSIS). The issue body is the source of truth for that block, so the fix is editing #326, not the file. It has been failing the daily cron for several days and blocks every prompt change.

@suleimansh
suleimansh marked this pull request as ready for review July 26, 2026 11:17
@suleimansh
suleimansh merged commit e5014ac into main Jul 26, 2026
1 of 2 checks passed
@suleimansh
suleimansh deleted the suleimansh/feat/1208-update-tickets branch July 26, 2026 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update tickets/ from GitHub

1 participant