lumnik. / scenarios All scenarios · Français

A ticket closed in Gitea stays open in the hub if only open ones are read

Goal Follow a Gitea forge's open tickets, paged by its Link header, without keeping open a ticket support has closed.

Support tracks its customer tickets in Gitea. Its API pages like GitHub's: the next page is in the Link header. lumnik follows the header, page after page:

manifests/lab-docker-compose.yml
# The support team's forge: a real Gitea, its issues are the customer tickets.name: lumnik-gitea-deskservices:  gitea:    image: gitea/gitea:1.22.6    environment:      GITEA__security__INSTALL_LOCK: "true"      GITEA__database__DB_TYPE: sqlite3      # The address the hub reaches: Gitea builds its Link headers from it.      GITEA__server__ROOT_URL: http://host.docker.internal:${GITEA_PORT:-3030}/      GITEA__api__DEFAULT_PAGING_NUM: "30"    ports: ["127.0.0.1:${GITEA_PORT:-3030}:3000"]   # this machine only: the admin password is the lab's    tmpfs: ["/data"]
manifests/10-tickets.yaml
apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-desk-a  connector_type: rest-generic  scopes: [support]spec:  base_url: http://host.docker.internal:3030/api/v1  auth: { kind: bearer, token_env: SCENARIO_DESK_TOKEN }  endpoints:    - id: open-tickets      path: /repos/desk-admin/support/issues?state=open&type=issues      method: GET      pagination: { kind: link-header, response_path: $ }      target:        kind: hybrid        table: ext.scenario_desk_a        unique_key: $.number        promote:          - { name: title, path: $.title, type: text }          - { name: client, path: "$.labels[?(@.name =~ /client:.*/)].name", type: text }          - { name: blocking, path: "$.labels[?(@.name == 'bloquant')].name", type: text }          - { name: opened_at, path: $.created_at, type: timestamptz }
$ lm connector run scenario-desk-a
run scheduled: 7de8ffc5-8dd3-480b-b153-a8580b987f8e

$ lm run get 7de8ffc5-8dd3-480b-b153-a8580b987f8e
ID        CONNECTOR        ENDPOINT      STATUS     IN  OUT  SKIPPED  DEAD-LETTERS  STARTED           ENDED             EVALUATED
7de8ffc5  scenario-desk-a  open-tickets  Completed  60  60   0        0             2026-10-30 15:05  2026-10-30 15:05  yes
manifests/20-tickets-all.yaml
apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-desk-a  connector_type: rest-generic  scopes: [support]spec:  base_url: http://host.docker.internal:3030/api/v1  auth: { kind: bearer, token_env: SCENARIO_DESK_TOKEN }  endpoints:    - id: open-tickets      path: /repos/desk-admin/support/issues?state=all&type=issues      method: GET      pagination: { kind: link-header, response_path: $ }      incremental: { mode: watermark, param: since, watermark_path: $.updated_at }      target:        kind: hybrid        table: ext.scenario_desk_a        unique_key: $.number        promote:          - { name: title, path: $.title, type: text }          - { name: state, path: $.state, type: text }          - { name: client, path: "$.labels[?(@.name =~ /client:.*/)].name", type: text }          - { name: blocking, path: "$.labels[?(@.name == 'bloquant')].name", type: text }          - { name: opened_at, path: $.created_at, type: timestamptz }          - { name: updated_at, path: $.updated_at, type: timestamptz }
$ lm connector run scenario-desk-a
run scheduled: a03d4fd3-3af4-4edf-81b3-9c5d1f691da6

$ lm run get a03d4fd3-3af4-4edf-81b3-9c5d1f691da6
ID        CONNECTOR        ENDPOINT      STATUS     IN  OUT  SKIPPED  DEAD-LETTERS  STARTED           ENDED             EVALUATED
a03d4fd3  scenario-desk-a  open-tickets  Completed  15  15   0        0             2026-10-30 15:05  2026-10-30 15:05  yes

Limits

  • A ticket deleted in Gitea is no longer returned: its row stays in the hub, with its last state.
  • The reread margin is a minute plus the run's length: a ticket changed just before is reread, never lost.
  • This replay does not declare X-Total-Count as the count's witness; total_header allows it since icreated/lumnik#1632, see the WordPress scenario.
  • Demo Gitea forge, synthetic tickets.

Support · customer tickets

Support closes 12 tickets.

The dashboard still counts 72 open.

Demo Gitea forge

The problem

A filter does not see what leaves it.

  • The import reads open tickets only.
  • A closed ticket is no longer returned.
  • Its row stays open.

What is not changed

Gitea stays as it is.

  • A read-only token.
  • Gitea's paging, followed by the Link header.

What had to be written

path: …/issues?state=all
pagination: { kind: link-header }
incremental: { mode: watermark, param: since }

Every state, only what changed.

One 28-line connector.

Red

Gitea: 60 open. The hub: 72.

  • The run is green.
  • 12 closed tickets stay open.

Green

Gitea: 57 open. The hub: 57.

  • The next day, 15 tickets reread, not 95.
  • The state is a column: closed reads closed.

The limit

A deleted ticket is not seen.

  • Gitea no longer returns a deleted ticket.
  • Its row stays in the hub, with its last state.

Try it

The scenario replays locally.

A real Gitea forge, every step published with its output.

This scenario runs on lumnik Open, free and open source.

Try lumnik Open →