Skip to content

fix(engine): report which PR read steps degraded instead of hiding it - #280

Merged
clickmatos merged 2 commits into
mainfrom
fix/pr-read-degradation-signal
Oct 8, 2026
Merged

clickmatos merged 2 commits into
mainfrom
fix/pr-read-degradation-signal

Conversation

@cpenaforte-clickbus

@cpenaforte-clickbus cpenaforte-clickbus commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Contexto e Motivação

Quando um passo da leitura de PRs do GitHub falha, o engine cai para dado parcial e não diz que caiu. A #236 registrou dois runs do mesmo engine, no mesmo repositório e com minutos de diferença, saindo com merge_strategy_dominant_share 1.0 e 0.845 e flow_pr_count ausente e 52 — por um 504 do GraphQL que nenhum campo do JSON denunciava. Quem lê não tem como distinguir "este repositório faz squash em todo merge" de "o enriquecimento falhou", que são conclusões opostas.

Resolver agora porque a #215 vai reescrever esse mesmo passo da busca, e o critério de aceite dela — metrics.json idêntico antes e depois — só é confiável se dois runs do mesmo código não puderem divergir em silêncio.

Closes #236

Mudanças

  • iris/models/pull_request.py — PullRequestFetch: os PRs e os passos que degradaram (basic, enrichment, reviews, fetch). Ausência de gh ou de remote GitHub não é degradação: o Iris funciona sem PRs, e isso continua sem sinal.
  • iris/ingestion/github_reader.py — _fetch_prs passa a devolver os passos que falharam; o enriquecimento por GraphQL distingue fim normal de parada por erro, inclusive quando a primeira página veio e a segunda falhou; e cada página do enriquecimento ganha uma nova tentativa após 2 s, para o 504 intermitente que o próprio código já documentava. A passada de reviews deixa de confundir falha (None) com estado sem PR ([]).
  • iris/ingestion/window_cache.py — o cache multi-janela guarda o fetch inteiro, então uma janela estreita servida do cache herda a degradação do fetch largo de onde saiu.
  • read_pull_requests_with_fallback — o except Exception: prs = [] que cli.py e org_runner.py repetiam vira uma função só, que mantém o run de pé e marca fetch.

A saída ainda não muda: o sinal é produzido e testado aqui, e emitido no metrics.json e no relatório pelo PR empilhado sobre este.

Plano de Teste

  • pytest tests/test_pr_fetch_degradation.py tests/test_window_cache.py -v — 24 testes: gh falso que lança CalledProcessError, como o real com check=True; retry que recupera e que não recupera; segunda página falhando depois da primeira; resposta sem data; reviews [] contra None; união dos passos entre os três estados; cache preservando a degradação; e a ausência de gh ou de remote sem sinal nenhum — rodado em b4a936e: 24 passed
  • pytest tests/ -q — a suíte inteira, com o portão de determinismo da [FEAT] Comparador de paridade do metrics.json, campo a campo #214 — rodado em b4a936e: local: 359 passed; a única falha é a test_status_reflects_state ([BUG] test_agent_settings_hook lê o spool real — falha em máquina com registros pendentes #146), que só ocorre fora do CI, e o test_hook_binders.py ficou de fora por depender de rede; no CI o comando literal passa

A verificação roda fora da máquina de quem construiu: o job cli do CI executa pytest tests/ -q a cada push (RFC 0029, PM-4).

Impacto e Risco

  • Áreas afetadas: a leitura de PRs do engine (github_reader.py, window_cache.py) e os dois pontos de entrada que a chamam (cli.py, org_runner.py).
  • Compatibilidade: o metrics.json de qualquer run sai idêntico ao de hoje. read_pull_requests muda o tipo de retorno de list[PullRequest] para PullRequestFetch; os dois únicos chamadores do repositório estão atualizados.
  • Performance: a única mudança observável é a nova tentativa do enriquecimento — 2 s por página do GraphQL que falhar na primeira chamada, e só nos runs que sem ela perderiam esse dado.
  • Segurança: nenhuma superfície nova; nenhum dado novo coletado.
  • Acessibilidade e custo AWS: N/A — sem UI e sem infraestrutura.

Sobre o tamanho (RFC 0028 §2.4): 470 linhas, faixa 251–500. O tipo novo e a detecção que o preenche formam um contrato só: separá-los deixaria na main um PullRequestFetch cujo campo de degradação nunca é preenchido. A #236 já foi dividida em PRs empilhados justamente por tamanho.

Plano de Rollback

git revert do merge deste PR. Sem migração, sem feature flag, sem estado a desfazer. Se os PRs empilhados sobre este já tiverem entrado, eles são revertidos antes.

Referências

Autoria Assistida

  • Agente: Claude Code — Claude Haiku 4.5 nos commits; Claude Sonnet 5.5 nas revisões por task; Claude Opus 5.5 na orquestração e na revisão final do conjunto
  • Escopo da assistência: desenho do sinal e implementação guiada por testes; a decisão sobre a merge strategy, no PR seguinte, foi do autor
  • Revisão humana: pendente — o autor revisa antes do merge

🤖 Generated with Claude Code

cpenaforte-clickbus and others added 2 commits October 5, 2026 14:31
When a step of the GitHub PR read fails, the engine falls back to partial
data and says nothing. Before any step can report that, the read needs a
place to carry it.

read_pull_requests now returns a PullRequestFetch: the PRs, plus the steps
that degraded on the way. The multi-window cache stores the whole fetch, so
a narrower window sliced from a wide one inherits its degradation. The
`except Exception: prs = []` that cli.py and org_runner.py both inlined
becomes read_pull_requests_with_fallback, which keeps the run going and
marks the fetch step instead of swallowing the error. A missing gh or a
missing GitHub remote is absence by design, not degradation, and stays
unflagged.

Refs #236

Assisted-by: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Each fallback in the PR fetch was silent. A failed basic pass dropped a
state's PRs, a failed GraphQL enrichment left PRs without commit refs or
merge-commit parents, a failed reviews pass left them without reviews -
and nothing in the output said so. Two runs of the same engine on the same
repo could disagree on merge strategy and flow metrics with no explanation.

_fetch_prs now returns the passes that failed. The enrichment pass tells a
normal end from a stop on error, including a later page failing after the
first succeeded, and the reviews pass stops conflating a failure (None)
with a state that has no PRs ([]). The enrichment query, which GitHub
answers with intermittent 504s, gets one retry after a short pause before
it gives up.

Refs #236

Assisted-by: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@cpenaforte-clickbus cpenaforte-clickbus added type: tech-debt Código sub-ótimo conhecido, workaround, ou cleanup pendente ai-assisted PR gerado total ou parcialmente com auxílio de agente de IA (RFC 0028 §2.2.7) labels Oct 5, 2026
@vercel

vercel Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
clickbus-iris Ready Ready Preview Oct 5, 2026 6:23pm UTC

Request Review

@cpenaforte-clickbus cpenaforte-clickbus self-assigned this Oct 5, 2026
@clickmatos
clickmatos merged commit bbf19f9 into main Oct 8, 2026
4 checks passed
@clickmatos
clickmatos deleted the fix/pr-read-degradation-signal branch October 8, 2026 13:32
@clickmatos
clickmatos restored the fix/pr-read-degradation-signal branch October 8, 2026 13:35
@clickmatos
clickmatos deleted the fix/pr-read-degradation-signal branch October 8, 2026 13:35

This branch was successfully deployed

1 active deployment
Preview — b4a936e8 Deployed Oct 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted PR gerado total ou parcialmente com auxílio de agente de IA (RFC 0028 §2.2.7) type: tech-debt Código sub-ótimo conhecido, workaround, ou cleanup pendente

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DEBT] PR enrichment degrades silently when the GraphQL step fails; the report carries no signal

2 participants