Summary
The GitHub issue index (/github/repos/<owner>/<repo>/issues/_index.json) is not emitting the labels field that this adapter's own layout contract documents. Consumers that filter by label on the index therefore cannot use it, and must fall back to opening every issue's meta.json.
This is currently breaking Factory's readiness reconciliation in production.
The contract says labels should be there
packages/github/src/layout-prompt.ts:33 documents the issue index row as:
{ "id": "<id>", "title": "<human-readable>", "updated": "<iso8601>", "number": 42, "state": "open", "labels": ["factory"] }
labels carries the issue's label names inline so you can filter on the index without opening every meta.json
and :54 gives the worked example, using this exact label:
jq '.[] | select(.labels | index("factory"))' /github/repos/octocat/hello-world/issues/_index.json
What is actually emitted
Live row from /github/repos/AgentWorkforce/factory/issues/_index.json:
{"id":"256","number":256,"state":"closed","title":"[factory] Writeback cleanup reports flushExitCode 1 ...","updated":"2026-08-14T22:58:08Z"}
keys present: id, number, state, title, updated
labels: ABSENT
0 of 12 rows in that index carry labels.
Why this breaks a real consumer
AgentWorkforce/factory already implements the documented fast path. src/orchestrator/factory.ts:5733 (#githubIssuePathsFromIndex) reads the index and filters:
if (state !== 'open' || !labels.some((label) => label.trim().toLowerCase() === requiredLabel)) {
continue
}
paths.push(`.../issues/by-id/${number}.json`)
But it guards against a legacy index, deliberately failing open:
// Labels were added to the public GitHub issue index contract after the first
// index version. Fall back for the entire repository if any row is legacy or
// malformed so an eligible issue can never be filtered out.
if (... || !Array.isArray(labels) || ...) {
return undefined
}
So the observed chain in production is:
- Index rows lack
labels
- Factory correctly judges the index unusable and returns
undefined
- It falls back to a full walk — 1,362 issues, read serially, observed live as
GitHub ready issue read progress {"read":215,"total":1362}
- The walk cannot finish inside the 5-minute discovery sweep lease
- The sweep loses its lease before checkpoint and throws
- The periodic readiness reconciliation fails every 60 seconds, indefinitely
Factory behaves correctly at every step. The defect is upstream, here.
Tracked on the consumer side as AgentWorkforce/factory#267. Two issues are currently stuck undispatched because of it (AgentWorkforce/relay#1528, AgentWorkforce/factory#267), and relay#1528 is the chain unblock for an entire workstream.
Asked-for behaviour
- Emit
labels on issue index rows, per the documented contract. Pull rows may omit it, as the contract already allows.
- Backfill existing indexes. The comment in Factory calls labels "added after the first index version," so existing rows likely predate the field. A fix that only covers newly-written rows leaves every current index unusable, and the consumer's fail-open guard rejects an index if any row is legacy — so a partial backfill is equivalent to no backfill.
- Determine whether the emitter writes it at all today. Before backfilling, establish whether this is (a) never implemented, (b) implemented and regressed, or (c) implemented but not reaching this deployment. The fix differs in each case, and the contract being documented is not evidence the emitter honours it.
Definition of done
/github/repos/<owner>/<repo>/issues/_index.json rows carry labels for issues, verified against a live projection rather than a unit test.
- Existing indexes are backfilled, so
jq '.[] | select(.labels | index("factory"))' returns matches against the real projection today.
- A consumer filtering on the index no longer needs to open
meta.json per issue.
- Verify with
AgentWorkforce/factory's reconcile: the readiness sweep should complete rather than exceed its lease, and relay#1528 should dispatch without manual intervention.
Verification — against the live projection
Do not close this on a unit test asserting the emitter's output shape.
jq '.[0] | keys' /github/repos/AgentWorkforce/factory/issues/_index.json
jq '[.[] | select(.labels | index("factory"))] | length' /github/repos/AgentWorkforce/factory/issues/_index.json
The first must include labels; the second must be non-zero (that repo has open factory-labeled issues today). Attach real output.
Constraints
- Branch from
origin/main. mergePolicy: never — open the PR and stop at review.
- PR publication is currently failing intermittently with a 403 (
AgentWorkforce/relayfile-cloud#155). If this issue's PR does not publish, report it rather than working around it — the branch will still hold the work.
- Note: this repo was added to Factory's routing config today specifically so this issue could be dispatched. If dispatch misbehaves for reasons unrelated to the change, say so rather than working around it.
Summary
The GitHub issue index (
/github/repos/<owner>/<repo>/issues/_index.json) is not emitting thelabelsfield that this adapter's own layout contract documents. Consumers that filter by label on the index therefore cannot use it, and must fall back to opening every issue'smeta.json.This is currently breaking Factory's readiness reconciliation in production.
The contract says labels should be there
packages/github/src/layout-prompt.ts:33documents the issue index row as:{ "id": "<id>", "title": "<human-readable>", "updated": "<iso8601>", "number": 42, "state": "open", "labels": ["factory"] }and
:54gives the worked example, using this exact label:What is actually emitted
Live row from
/github/repos/AgentWorkforce/factory/issues/_index.json:{"id":"256","number":256,"state":"closed","title":"[factory] Writeback cleanup reports flushExitCode 1 ...","updated":"2026-08-14T22:58:08Z"}0 of 12 rows in that index carry
labels.Why this breaks a real consumer
AgentWorkforce/factoryalready implements the documented fast path.src/orchestrator/factory.ts:5733(#githubIssuePathsFromIndex) reads the index and filters:But it guards against a legacy index, deliberately failing open:
So the observed chain in production is:
labelsundefinedGitHub ready issue read progress {"read":215,"total":1362}Factory behaves correctly at every step. The defect is upstream, here.
Tracked on the consumer side as
AgentWorkforce/factory#267. Two issues are currently stuck undispatched because of it (AgentWorkforce/relay#1528,AgentWorkforce/factory#267), andrelay#1528is the chain unblock for an entire workstream.Asked-for behaviour
labelson issue index rows, per the documented contract. Pull rows may omit it, as the contract already allows.Definition of done
/github/repos/<owner>/<repo>/issues/_index.jsonrows carrylabelsfor issues, verified against a live projection rather than a unit test.jq '.[] | select(.labels | index("factory"))'returns matches against the real projection today.meta.jsonper issue.AgentWorkforce/factory's reconcile: the readiness sweep should complete rather than exceed its lease, andrelay#1528should dispatch without manual intervention.Verification — against the live projection
Do not close this on a unit test asserting the emitter's output shape.
The first must include
labels; the second must be non-zero (that repo has openfactory-labeled issues today). Attach real output.Constraints
origin/main.mergePolicy: never— open the PR and stop at review.AgentWorkforce/relayfile-cloud#155). If this issue's PR does not publish, report it rather than working around it — the branch will still hold the work.