Přeskočit na obsah

Tato stránka byla automaticky přeložena z angličtiny, jediného jazyka, ve kterém se tento web píše. Číst originál.

Backlog

čtyři veličiny, které rostou samy — otevřené pull requesty, worktrees, větve, běžící servery; proč GitHub nikdy nemůže mergnout pull request tohoto repozitáře a co jej přistane místo toho; příkazy, které každou z nich měří a snižují

Vykresleno z docs/BACKLOG.md — stejný Markdown, jaký ukazuje GitHub.

Pět veličin v tomto repozitáři, které rostou samy, příkazy, které je měří, a příkazy, které je zmenšují. Chování tak, jak je implementováno a testováno; kde se tento dokument a skripty rozcházejí, dokument se mýlí a mění se ve stejném commitu. Pravidlo je project.accumulation-is-measured; kontrola je scripts/ci/backlog-check; behaviorální případy jsou test/cases/131_backlog_hygiene.sh a test/cases/276_disk_is_bounded.sh.

Zjištění, kvůli kterému to existuje

2026-09-11 operátor nahlásil jeden příznak — worktrees, procesy a větve se hromadí a nic se nenasazuje — a měření našlo čtyři oddělené veličiny najednou (pátá, disk, zaplnila svazek následující den a má níže vlastní sekci):

MnožstvíZměřenoZ toho
otevřené pull requesty1615 se čistě sloučilo na jakémkoli vývojářském stroji
linked worktrees6447 s nepotvrzenou prací
lokální větve11191 nesloučeno do origin/master
běžící servery majordomus19214 opuštěných, jeden starší než den, 13 portů, ~150 MB

Nasazovací pipeline nebyla rozbitá: gh-pages bylo o jeden commit za master a web běžel. Nic neselhávalo. Každá část fungovala tak, jak byla navržena, a výsledkem byl přesto stav bez pohybu — což je smyslem tohoto dokumentu.

Proč se pull requesty na GitHubu nemergují a nikdy nebudou

Toto úložiště commituje své odvozené artefakty a každý z nich nese otisk stromu, ze kterého byl vygenerován. Po merge tento strom není stromem žádného z rodičů, takže oba zaznamenané otisky jsou špatně a žádná textová kombinace z nich není správná — správná hodnota existuje až po spuštění generátoru. .gitattributes proto tyto cesty označí merge=derived a scripts/merge-derived je vyřeší místo konfliktu.

Vlastní merge driver je spustitelný soubor jmenovaný v git config merge.<name>.driver. Tato konfigurace je pro každý klon a nelze ji commitnout — proto existuje just derive-merge-driver. Servery GitHubu jsou klon, který ji nikdy nespustil. GitHub tedy provede prostý třícestný merge nad soubory, které každá větev přegenerovává, najde konflikty ve všech a ohlásí pull request jako CONFLICTING / DIRTY.

CONFLICTING na GitHubu není falešně pozitivní. Konflikt je skutečný pro každý klon, který nemá driver, a GitHub je vždy takový klon. Táž větev, změřená oběma způsoby:

git                               merge-tree --write-tree origin/master <branch>  # exit 0
git -c merge.derived.driver=false merge-tree --write-tree origin/master <branch>  # exit 1, 15 conflicts

Na otázku „sloučí se to“ tedy existují dvě poctivé odpovědi a která z nich se dostane k vám, závisí na tom, zda je driver nakonfigurován. Ta, na které záleží, je klon, který výsledek skutečně ponese — vývojářův stroj — a tam byla odpověď ano u patnácti z šestnácti toho dne otevřených. Tlačítko merge není pomalé ani nespolehlivé; je strukturálně nedostupné a dostupným se nestane. Přispěvatel, který na ně čeká, čeká navždy, a toto čekání je celý backlog.

Zeptejte se gitu na návratový kód, nikdy nehledejte grepem v jeho výstupu. Znění konfliktu u merge-tree není smlouva; grep, který ho mine, započítá konfliktní merge jako čistý, což je nejdražší způsob, jak může být toto měření špatně. majordomus prs používá návratový kód.

Příkazy

MnožstvíMěřeníZredukovat
pull requestymajordomus prs statusmajordomus prs drain --refresh
worktrees, větvemajordomus worktreeúklid majordomus worktree, ručně u špinavých
opuštěné serveryscripts/reap-orphansscripts/reap-orphans --kill
výstup buildu na diskuscripts/reap-orphans --targetsscripts/reap-orphans --reclaim
celá kontrolascripts/ci/backlog-checkscripts/ci/backlog-check --remote

majordomus prs

Integrace pull requestů je typovaný podsystém spustitelného souboru (docs/INTEGRATION.md, ADR 0101). majordomus prs refresh pozoruje forge a stahuje každou otevřenou hlavu; status, plan a explain klasifikují každý pull request proti aktuálnímu masteru gitem a odvozeným driverem — nikdy ne mergeable forge — offline; drain mergne další připravený, ověří, že přistál, a před čímkoli dalším znovu pozoruje, jeden merge po druhém. drain --refresh přinese master do prvního pull requestu, který ho potřebuje, když nic není připraveno, po jednom, protože merge čehokoli jiného mezitím by ho zase dostal pozadu.

Nahradilo scripts/land, které sloučilo každý čistý pull request na jednu integrační větev: dávka, jejíž regrese nikdo nedokázal přiřadit, plánovaná ze seznamu, který první merge zastaral.

Měření běží na hodinách: .github/workflows/queue.yml (model .ai/repo/ci/queue.yaml) spouští majordomus prs refresh a majordomus prs status každých třicet minut a publikuje frontu do souhrnu jobu, takže její stav je měření, nikoli něco, na co si někdo vzpomene zeptat. Nikdy nespouští drain ani cleanup --apply a jeho token nemůže zapisovat: stav je automatický, merge zůstává rozhodnutím. Token jen pro čtení nemůže přečíst ani ochranu větve, takže to report řekne (exit 10) a nic nenazve ready; job selže jen u nepoužitelného měření (exit 12). test/cases/350_the_queue_measures_itself.sh drží obě poloviny a selže, pokud workflow získá jednající příkaz nebo oprávnění k zápisu.

scripts/reap-orphans

majordomus serve nebo mcp přežije svého klienta, kdykoli klient zemře bez jeho zavření: rodič je sklizen, server je přepodřízen procesu init a drží svůj port i paměť do restartu stroje. Server, který odpovídá, není server, s nímž někdo mluví.

Relace v tomto repozitáři jsou navzájem svými nástroji, takže proces se nikdy neshoduje podle jména. pkill -f majordomus by ukončilo servery, ke kterým jsou připojeni živí peers. Proces se ukončí, jen když platí všechny čtyři podmínky, každá znovu ověřená těsně před signálem:

  • je to proces majordomus serve nebo mcp, podle cesty ke spustitelnému souboru;
  • jeho rodičem je init (ppid 1) — relace, která ho spustila, zanikla;
  • má nula TCP spojení ESTABLISHED — není připojen žádný klient;
  • je starší než --min-age (výchozí 30m).

Ve výchozím stavu nanečisto; --kill provede akci. Server, který získá klienta mezi skenem a signálem, se přeskočí.

Souhlas také vybírá svůj předmět. Bez --servers/--targets zkoumá --kill samotné servery a --reclaim samotný výstup buildu; bez jakýchkoli příznaků se hlásí obojí a nic se nedotkne. Není to kosmetika. Běh --reclaim jednou vypsal tabulku živých serverů pod slovy would reap, což svádí čtenáře věřit, že souhlas, který dal pro výstup buildu, se chystá ukončit jeho server — nebo už ukončil. Souhlas pojmenovaný pro jeden předmět nikdy netabuluje předmět druhého.

Úklid, který se zastaví před svým shrnutím, říká INCOMPLETE a jmenuje, který ze dvou předmětů zkoumal a který nikdy nedosáhl. Hlavička sloupců s řádky pod sebou má tvar odpovědi a stav ukončení je jediné, co říká opak — což nikdo z terminálu nečte. 2026-09-13 běh vypsal hlavičku, jeden server a zemřel na SIGPIPE s exitem 141 z nedostatku čtenáře, který by četl svůj proud do konce; považoval se za report (project.a-verdict-states-its-subject, test/cases/318_a_partial_sweep_says_so.sh).

Výstup sestavení a podlaha pod sestavením

Dne 2026-09-12 tento stroj dosáhl nula volných bajtů na svazku o velikosti 926 GiB. Každá relace na něm zemřela ve stejné minutě — ne elegantně, ale s harnessem neschopným zapsat vlastní soubory úloh, což se každé relaci jeví jako rozbitý tooling, ne jako plný disk, a df nešlo spustit také. ~/dev/prismatic-majordomus-wt držel 180 GB, z toho ~145 GB tvořilo apps/majordomus-cli/target ve worktrees, jejichž větve už byly sloučeny. Jeden přistál před hodinami a stále držel 32 GiB. Nic v dokončení práce neodstraní adresář sestavení, který vytvořila, takže by to neudělalo nikdy nic.

Odpovídá na to tentýž reaper, s druhým subjektem a druhým souhlasem (--reclaim, nikdy ne --kill). Odstraňuje target/ a nikdy worktree: falešně pozitivní nález někoho stojí přestavbu a nemůže stát ani bajt zdroje. Adresář sestavení se získává zpět jen tehdy, když platí všechna čtyři — mergnuto do origin/master nebo pushnuto (špička je v remote-tracking refu), nikdo v tom worktree nepracuje, git status je prázdné a adresář je prokazatelně vlastním adresářem cargo uvnitř onoho worktree. Pushnuto bylo přidáno 2026-10-10, kdy forma jen pro mergnuté mohla získat zpět 3 z 31 adresářů sestavení, zatímco 270 GB leželo ve větvích pushnutých a čekajících ve frontě přistání nebo v odpojených offload stromech a dva stroje dosáhly najednou 99 %. Primární checkout je vyloučen jménem, ne predikátem, protože jeho spustitelný soubor si půjčuje každý jiný checkout.

Živost se čte ze stroje (ps a pracovní adresář každého procesu) a nikdy z nástěnky peerů: oznámení patří připojení a pracovník, který se znovu připojí, si ponechá práci, ale ztratí své místo na nástěnce — přesně okno, v němž reaper běží. Samotná mergnutost by byla katastrofa a ten případ nastal na stroji toho rána: worktree o 11,8 GB se živým peerem uvnitř se četl jako plně mergnutý, protože relace v něm pracující ještě necommitovala a jeho špička byla stále tam, kde ji merge nechal. mtime tu není nikdy predikátem (je to ten, jehož chybějící vstup se změní na extrémní hodnotu, a jednou vyčistil tři aktivní worktrees) a reaper jako celek odmítá — nezískává zpět nic, ne všechno — když nelze přečíst živost nebo origin/master.

Druhá polovina je mez, ne varování. df v doctor pomůže další osobě a nemůže pomoci té, která už uvízla. mj_rust_space_check v lib/rust_bin.sh odmítne zahájit build pod 5120MB volného místa a všechny tři cesty, které jeden spouštějí — launcher, scripts/derive, just build — se na to ptají. Dolní mez je změřena: plný cargo build --locked crate do prázdného target/ zapsal 2,45GiB a dolní mez jsou dva takové. MAJORDOMUS_MIN_FREE_MB=0 ji zruší. Mez, jejíž vlastní vstup nelze změřit, to řekne a nechá build běžet — záměrný opak úklidu, protože odmítnout každý build na stroji, jehož df je nečitelný, zastaví veškerou práci kvůli hypotetické hrozbě.

Samotná podlaha je okraj útesu: odmítne, až když nic nezbývá, čím by odmítla, a reaper je příkaz, na který si někdo musí vzpomenout. Pod horní hranicí — MAJORDOMUS_RECLAIM_BELOW_MB, výchozí 51200MB, deset buildů v jejich mediánové pracovní velikosti — proto tatáž kontrola nejdřív sama spustí scripts/reap-orphans --targets --reclaim, nejvýš jednou za deset minut na stroj, a teprve pak uplatní podlahu. Zametání se děje, protože se dějí buildy. Reaper, který chybí nebo odmítne, build nikdy neshodí. test/run.sh nastavuje hranici na 0, protože případ nesmí jednat na stroji, který měří; případ 276 zkouší reklamaci na vlastní fixture.

Odvozené stromy, které nikdy nebyly deklarovány

merge=derived pomáhá jen cestě, která je označena. scripts/generate-site-data při každém běhu maže a znovu generuje čtrnáct celých sekcí pod site/content/ — jeho řádky rm -rf "$CONTENT_ROOT/..." jsou seznam — a 2026-09-11 třináct ze čtrnácti nebylo deklarováno v .gitattributes. Deklarováno bylo jen registry.

Každá integrace tak kolidovala v guarantees/, commands/, use-cases/, why/, plan/, skills/ a dalších: obsah, který nevlastní žádná větev, který generátor přepisuje celý a který pak člověk řešil ručně, při každém merge, donekonečna. Byla to po samotném omezení forge největší mechanická příčina nahromaděné fronty a vyšla najevo tady jen proto, že ji přistání šesti pull requestů zasáhlo hned při prvním merge origin/master.

Všech čtrnáct je nyní deklarováno a scripts/ci/backlog-check čte seznam z generátoru, místo aby jej vedlo ručně — ručně vedený seznam zaostane poprvé, když se přidá sekce, a právě tak třináct vynechání zůstalo nepovšimnuto.

Proč byly servery vůbec opuštěny

majordomus serve --idle N se zastaví, když k němu nebyl N sekund připojen žádný peer, a DEFAULT_IDLE_SECONDS (900) je konstanta, se kterou se spouští sdílený server — scripts/ci/entry-converges drží tento kontrakt pro server, který serve ensure spouští, což je cesta, kterou jde člověk.

Neunikla cesta. --idle syrového serve má výchozí hodnotu 0 — běžet do zastavení a čtyři případy sady spustily server na pozadí bez něj. Případ přerušený před spuštěním úklidového trapu — zabitý běh, limit útraty, zavřený notebook — nechá ten server držet port do restartu stroje. Odtud se opuštěné servery braly a proto nyní stejný kontrakt drží pro vlastní spouštění sady scripts/ci/backlog-check, nikoli jen pro vstupní cestu. Dvě poloviny jednoho pravidla, kontrolované jen jedním směrem, jsou tu opakující se tvar.

Worktree a větve

majordomus worktree odvozuje topologii z gitu (project.worktree-topology) a označuje, které větve jsou způsobilé k úklidu. Počet způsobilých je obvykle mnohem nižší než počet těch, které vypadají zbytečně: v den uvedený výše 5 ze 111. Nula unikátních commitů není důkaz, že je větev bezpečné smazat — sloučená větev může držet nezacommitovanou a nesledovanou autorskou práci a 47 ze 64 worktrees bylo špinavých. Klasifikujte pracovní strom, nejen historii, a před odstraněním čehokoli pushněte.

Co kontrola drží

Runner CI nemá žádné worktree, žádné opuštěné servery a žádnou vlastní nevyřízenou frontu, takže backlog-check ta množství neměří. Drží pohromadě věci, jejichž absence nechala ta množství nepozorovaně narůstat: že každé množství má stále svůj příkaz, že reaper je bezpečný konstrukcí, že .gitattributes a jeho instalátor se stále shodují — deklarace kontrolovaná jen jedním směrem je způsob, jakým tato třída vad přežívá — a že žádný dokument neposílá čtenáře k tlačítku merge. --remote přidává samotné měření a selže, když je přistávatelných a stále otevřených víc než MJ_BACKLOG_MAX (výchozí 10) pull requestů.

Prohledává názvy a popisy všech stránek, které tento web publikuje.