10 interesting stories served every morning and every evening.

What Is a Dickover?

daringfireball.net

Please en­joy this ar­ti­cle on its own web­page. Trust me.

dick­over n. : a modal panel, popover, or cur­tain pre­sented by a web­site or app, de­lib­er­ately ob­scur­ing its own con­tent to frus­trate the user with an un­wanted, un­nec­es­sary, manda­tory in­ter­ac­tion; e.g. ask­ing the user to ac­cept cookies”, sub­scribe to a newslet­ter, in­stall the web­site’s mo­bile app, agree to terms of ser­vice, or any­thing else that the user could­n’t give two shits about.

You know what a dick­over is, even if you did­n’t know what to call it (until now). If you use the Internet, you en­counter them every day. They’re popovers, but dick­headed. The web is ab­solutely lousy with them, and mo­bile apps pre­sent them too, with in­creas­ing fre­quency.

Dickovers are a ver­i­ta­ble scourge. They’re so com­mon they’re ef­fec­tively part of the fir­ma­ment. I started call­ing these things dick­pan­els in 2022, but when dick­over popped into my head last week,1 I could­n’t shake the feel­ing that it’s a bet­ter term for these ubiq­ui­tous odi­ous ir­ri­ta­tions. You can hardly go any­where on the web with­out get­ting dicked over by a dick­over. They of­ten pester you about per­mit­ting cook­ies, like this one from Euronews or this one from Gallup. This ma­li­cious de­sign pat­tern is so ubiq­ui­tous that it has spread even to per­sonal blogs, like this one from my friend Om Malik, and to great brands like Field Notes, both ask­ing you to sign up for their newslet­ters.

The home­page for every sin­gle blog hosted by Substack shows a par­tic­u­larly per­ni­cious dick­over on its home­page. The Substack dick­over does­n’t even look like a panel. It’s a full-screen cur­tain de­signed and worded to sug­gest, strongly, that you need to sign up for the blog’s email newslet­ter just to read any­thing. The dis­missal but­ton for the Substack dick­over is a small text link — that does­n’t look any­thing like a but­ton — that says some­thing like No thanks” (e.g. Paul Krugman, Matt Yglesias) or some­thing that adds in­sult to in­jury with a cloy­ingly sac­cha­rine la­bel like Just gimme that con­tent!” (e.g. Volts).

Here’s one from The Philadelphia Inquirer, for which I pay $20/month to sub­scribe, ask­ing me to sign up for SMS text mes­sages about the Jersey shore, while I’m logged into their cursed web­site, be­fore they’ll let me see the ar­ti­cle I came to read. Every time I see one of these I think about un­sub­scrib­ing. I’m pay­ing them to abuse my time and at­ten­tion. I started cap­tur­ing screen­shots of every dick­over I saw when I started work­ing on this ar­ti­cle, and I soon had to give up be­cause I was col­lect­ing too many of them. But this one from Tom’s Hardware I ac­tu­ally en­joyed, be­cause their own dick­over got dicked over by one of their own fuck­ing ads in a JavaScript Z-axis slap­fight.

If you visit a web­site you should … see the web­site. See its con­tent. Be able to read the ar­ti­cle whose page you are at­tempt­ing to visit. Showing a subscribe to our newslet­ter” or accept our fuck­ing cook­ies” dick­over to some­one try­ing to read an ar­ti­cle on the web makes no more sense than send­ing out an email newslet­ter that only con­tains a link to read the newslet­ter on a web­page. A web­page should show the web­page. An email should show the email. I should not have to ex­plain this.

Some sites hit you with their dick­overs on page load, when you might be braced for it. We’re all braced for ob­sta­cles and an­noy­ances these days when we load web pages. But some sneaky, cow­ardly bas­tards sucker-punch you with their dick­bars only af­ter you have started read­ing, and be­gin to scroll down the page. Then, wham, they hit with their dick­over. It’s a god­damn priv­i­lege for any­one to be­stow your ar­ti­cle, story, or prod­uct page with their at­ten­tion. The gall, to de­lib­er­ately in­ter­rupt them while they are in the mid­dle of ac­tively read­ing, to pre­sent them with a dick­over. It is no dif­fer­ent from snatch­ing a phys­i­cal copy of a book or mag­a­zine out of a read­er’s hands in or­der to bad­ger them for some­thing other than the at­ten­tion they were al­ready grant­ing your work, ex­cept that the phys­i­cal act of snatch­ing a pub­li­ca­tion from a read­er’s hands would sub­ject you to be­ing punched in the face.

Addendums

Dickbars are re­lated to dick­overs, but are far lesser crimes against de­sign and user ex­pe­ri­ence. A dick­bar is a non-modal popover that ob­structs only a por­tion of the un­der­ly­ing con­tent, of­ten just a short hor­i­zon­tal strip. If dick­overs are de­sign felonies, dick­bars are mis­de­meanors. Here are typ­i­cal dick­bar ex­am­ples on desk­top and mo­bile lay­outs. Here’s a rel­a­tively at­trac­tive one from Apple on its Newsroom blog, and one from the ex­cel­lent Acquired pod­cast that is gra­cious enough to tuck it­self into a cor­ner. Here’s an ob­nox­ious one from the Four Seasons that is large enough to edge to­ward dick­over con­sid­er­a­tion. What makes dick­bars lesser of­fenses is that they do not ob­scure the en­tirety of the un­der­ly­ing con­tent, and thus do not de­mand manda­tory ac­tion to dis­miss them. You can view and scroll the page be­low them. What makes dick­bars crimes is that they still ob­struct and dis­tract. (Horizontal dick­bars, the most com­mon form, in­ter­fere with pag­ing via the space bar one screen­ful at a time. The con­tent scrolls by the height of the web­page, not the height of the web­page mi­nus the height of the dick­bar. Thus the dick­bar cov­ers un­read text each time you page down.)

Dickbars are re­lated to dick­overs, but are far lesser crimes against de­sign and user ex­pe­ri­ence. A dick­bar is a non-modal popover that ob­structs only a por­tion of the un­der­ly­ing con­tent, of­ten just a short hor­i­zon­tal strip. If dick­overs are de­sign felonies, dick­bars are mis­de­meanors. Here are typ­i­cal dick­bar ex­am­ples on desk­top and mo­bile lay­outs. Here’s a rel­a­tively at­trac­tive one from Apple on its Newsroom blog, and one from the ex­cel­lent Acquired pod­cast that is gra­cious enough to tuck it­self into a cor­ner. Here’s an ob­nox­ious one from the Four Seasons that is large enough to edge to­ward dick­over con­sid­er­a­tion. What makes dick­bars lesser of­fenses is that they do not ob­scure the en­tirety of the un­der­ly­ing con­tent, and thus do not de­mand manda­tory ac­tion to dis­miss them. You can view and scroll the page be­low them. What makes dick­bars crimes is that they still ob­struct and dis­tract. (Horizontal dick­bars, the most com­mon form, in­ter­fere with pag­ing via the space bar one screen­ful at a time. The con­tent scrolls by the height of the web­page, not the height of the web­page mi­nus the height of the dick­bar. Thus the dick­bar cov­ers un­read text each time you page down.)

All dick­overs are modal block­ers, but not all modal block­ers are dick­overs. A pay­wall sign-up / sign-in panel, for ex­am­ple, is not a dick­over. Paywalls are, at times, an­noy­ing, but one of the defin­ing as­pects of a dick­over is that they are un­nec­es­sary. Cookie per­mis­sions are un­nec­es­sary. Signing up for an email newslet­ter is un­nec­es­sary. But for pay­walled con­tent, ask­ing for sign-up / sign-in is nec­es­sary.

All dick­overs are modal block­ers, but not all modal block­ers are dick­overs. A pay­wall sign-up / sign-in panel, for ex­am­ple, is not a dick­over. Paywalls are, at times, an­noy­ing, but one of the defin­ing as­pects of a dick­over is that they are un­nec­es­sary. Cookie per­mis­sions are un­nec­es­sary. Signing up for an email newslet­ter is un­nec­es­sary. But for pay­walled con­tent, ask­ing for sign-up / sign-in is nec­es­sary.

The ne­ol­o­gism came to me when I de­cided to write about Dropover, a great lit­tle drag-and-drop shelf” util­ity for the Mac, shortly af­ter I posted an item com­plain­ing about a par­tic­u­larly ridicu­lous cookie modal blocker from Euronews. That Euronews post got me think­ing that dick­panel just was­n’t stick­ing. The dick part was right but the panel part lacked oomph. It did­n’t snap. Then I got to writ­ing about Dropover and it came to me. That night I ran a poll on Mastodon ask­ing What’s a bet­ter word for in-win­dow fake dialog box­es’ that web­sites (and some apps) use to cover up con­tent?” — dick­panel or dick­over — and, with 1,130 re­sponses, dick­over won by a slim 51 – 49 per­cent mar­gin. But af­ter sleep­ing on it I was con­vinced dick­over was the bet­ter term, no mat­ter how the poll turned out. Those who voted for dick­panel were, judg­ing from their com­ments, con­cerned that over” is­n’t de­scrip­tive enough. But what makes a ne­ol­o­gism stick is­n’t de­scrip­tive­ness or ob­vi­ous­ness, but us­age. If we use it, it’ll stick. And I, for one, want to use dick­over be­cause it stings, and it’s fun to sting peo­ple who are do­ing some­thing dick­headed. ↩︎

The ne­ol­o­gism came to me when I de­cided to write about Dropover, a great lit­tle drag-and-drop shelf” util­ity for the Mac, shortly af­ter I posted an item com­plain­ing about a par­tic­u­larly ridicu­lous cookie modal blocker from Euronews. That Euronews post got me think­ing that dick­panel just was­n’t stick­ing. The dick part was right but the panel part lacked oomph. It did­n’t snap. Then I got to writ­ing about Dropover and it came to me. That night I ran a poll on Mastodon ask­ing What’s a bet­ter word for in-win­dow fake dialog box­es’ that web­sites (and some apps) use to cover up con­tent?” — dick­panel or dick­over — and, with 1,130 re­sponses, dick­over won by a slim 51 – 49 per­cent mar­gin. But af­ter sleep­ing on it I was con­vinced dick­over was the bet­ter term, no mat­ter how the poll turned out. Those who voted for dick­panel were, judg­ing from their com­ments, con­cerned that over” is­n’t de­scrip­tive enough. But what makes a ne­ol­o­gism stick is­n’t de­scrip­tive­ness or ob­vi­ous­ness, but us­age. If we use it, it’ll stick. And I, for one, want to use dick­over be­cause it stings, and it’s fun to sting peo­ple who are do­ing some­thing dick­headed. ↩︎

reuters.com

www.reuters.com

Please en­able JS and dis­able any ad blocker

Anthropic surpasses OpenAI to become world’s most valuable AI startup

qazinform.com

13:21, 30 May 2026

Anthropic has be­come the most valu­able ar­ti­fi­cial in­tel­li­gence startup in the world, sur­pass­ing OpenAI in mar­ket val­u­a­tion. Following a new fund­ing round, the val­u­a­tion of the de­vel­oper be­hind the Claude AI as­sis­tant has ap­proached the $1 tril­lion mark, re­ports a Qaz­in­form News Agency cor­re­spon­dent.

Anthropic an­nounced that it had raised $65 bil­lion in a Series H fund­ing round. The largest in­vestors in­cluded Altimeter Capital, Dragoneer, Greenoaks, and Sequoia Capital.

Following the deal, Anthropic of­fi­cially over­took OpenAI in mar­ket val­u­a­tion and be­came the largest AI com­pany among Silicon Valley’s pri­vate star­tups.

The new val­u­a­tion is nearly three times higher than the com­pa­ny’s February val­u­a­tion, when Anthropic was es­ti­mated to be worth around $380 bil­lion. The fund­ing pack­age also in­cluded pre­vi­ously agreed in­vest­ments, in­clud­ing $5 bil­lion from Amazon.

The main dri­ver be­hind Anthropic’s growth is said to be the pop­u­lar­ity of its Claude AI as­sis­tant and the Claude Code ser­vice, which is widely used by soft­ware de­vel­op­ers. The com­pany re­ported that its an­nual rev­enue had grown to $47 bil­lion. Last year, the fig­ure stood at about $10 bil­lion.

At the same time, Anthropic in­tro­duced its new ar­ti­fi­cial in­tel­li­gence model, Claude Opus 4.8, as well as the closed sys­tem Claude Mythos Preview, which of­fers en­hanced cy­ber­se­cu­rity ca­pa­bil­i­ties for cor­po­rate clients.

Anthropic Chief Financial Officer Krishna Rao stated that de­mand for Claude prod­ucts con­tin­ues to grow rapidly around the world.

It is noted that Anthropic’s rise has in­ten­si­fied com­pe­ti­tion in the ar­ti­fi­cial in­tel­li­gence mar­ket. In March, OpenAI was val­ued at $852 bil­lion fol­low­ing a record $122 bil­lion fund­ing round. At the same time, the largest AI com­pa­nies are prepar­ing for pub­lic list­ings. According to CNBC, OpenAI may file for an ini­tial pub­lic of­fer­ing (IPO) within the com­ing weeks. Anthropic is also con­sid­er­ing a pub­lic stock of­fer­ing, al­though the ex­act tim­ing has not yet been dis­closed.

Earlier, Qazinform News Agency re­ported that Kazakhstan ranked among the coun­tries least con­cerned about job losses caused by ar­ti­fi­cial in­tel­li­gence, ac­cord­ing to the lat­est global sur­vey by the Gallup International Association.

pandoc-templates.org

pandoc-templates.org

pan­doc-jour­nal-tem­plates

sachsmc (Michael Sachs)

Journal tem­plates for sev­eral ma­jor jour­nals (incl. JASA, TAS, JBES, JCGS, SBP, Technometrics, Biometrical Journal, Biometrics, Biometrika, Biostatistics, AOAS, AOP, AAP, AOS, SSY, Journal of Statistical Software, Statistics in Medicine, and The R Journal).

OpenRouter Raises $113M Series B | OpenRouter

openrouter.ai

Skip to con­tent

/

Proposed new US funding rules: We can cancel any grant at any time

arstechnica.com

This is the end

Peer re­view now op­tional, po­lit­i­cal staff would screen grants for for­bid­den top­ics.

Russell Vought, di­rec­tor of the Office of Management and Budget (OMB), dur­ing a tele­vi­sion in­ter­view at the White House in Washington, DC, on Monday, July 7, 2025.

Credit:

Getty | Al Drago

Last August, the Trump ad­min­is­tra­tion is­sued an ex­ec­u­tive or­der in­tended to fun­da­men­tally al­ter how grant fund­ing is han­dled by the US gov­ern­ment. Under the sys­tem that had made the US a sci­en­tific su­per­power, peer re­view­ers rated the sci­en­tific qual­ity and fea­si­bil­ity of grant ap­pli­ca­tions, and sub­ject-mat­ter ex­perts within the fund­ing agen­cies used these rat­ings to de­ter­mine which grants got funded. Under the pro­posed rules, po­lit­i­cal ap­pointees would have the fi­nal say, and they were specif­i­cally in­structed not to routinely de­fer” to peer re­view­ers.

In the in­terim, the ad­min­is­tra­tion has lost many court cases be­cause it turns out that is­su­ing ex­ec­u­tive or­ders does­n’t cir­cum­vent le­gal re­quire­ments, and the or­ders can be va­cated if they lack strong jus­ti­fi­ca­tion. To avoid that same fate, the Office of Management and Budget (OMB) has de­cided to merge the ex­ec­u­tive or­der with other ad­min­is­tra­tion pri­or­i­ties and send it through the for­mal fed­eral rule­mak­ing process.

The re­sult is a hor­ror show for US sci­ence re­search. Not only is peer re­view made a sec­ondary con­sid­er­a­tion, but the new rules would al­low any fed­eral agency to can­cel any grant at any time based on the vague as­ser­tion that it is­n’t in the national in­ter­est.” The doc­u­ment would also ban any grants on a num­ber of cul­ture war top­ics, limit in­ter­na­tional col­lab­o­ra­tions, and block spend­ing on things like pub­lish­ing pa­pers and at­tend­ing con­fer­ences.

It is, in short, a recipe for how the gov­ern­ment can fin­ish the job of crip­pling American sci­ence.

Putting the OMB in charge

Previously, the rules gov­ern­ing grant­mak­ing were han­dled on an agency-by-agency ba­sis. The OMB is­sued over­all guid­ance, but the Department of Energy was­n’t ex­pected to fol­low the ex­act same pro­ce­dures that were de­vel­oped for the National Institutes of Health, to give two ex­am­ples. The new doc­u­ment is meant to change that sit­u­a­tion, turn­ing what had been guid­ance into rules. By pub­lish­ing them, the OMB is start­ing the for­mal rule­mak­ing process, which will then pro­ceed through pub­lic feed­back and a fi­nal rule pub­lished in the Federal Register.

The doc­u­ment it­self is an odd grab-bag of mi­cro­manag­ing grant processes, as­ser­tion of pres­i­den­tial power, and air­ing of cul­tural griev­ances. In many spots, it’s not even in­ter­nally con­sis­tent—it in­sists, for ex­am­ple, that Federal fi­nan­cial as­sis­tance must not dis­crim­i­nate on the ba­sis of the view­point,” and then turns around and com­plains that grants were of­ten used… to pro­mote a woke’ pol­icy agenda that did not re­flect the val­ues of the vast ma­jor­ity of the American pub­lic.”

Its lack of co­her­ence, how­ever, will not pre­vent it from caus­ing stag­ger­ing dam­age to the US sci­en­tific sys­tem.

For starters, it would for­mal­ize the dep­re­ca­tion of peer re­view as a fac­tor in de­cid­ing which grants to fund. Peer re­view re­mains ad­vi­sory and does not re­place agency dis­cre­tion,” the doc­u­ment states. That was al­ways tech­ni­cally true, as agen­cies like the NIH and National Science Foundation re­served the op­tion of fund­ing some lower-scor­ing grants if ex­perts within those agen­cies felt they had merit that the re­view­ers had over­looked. But those were con­sid­ered ex­cep­tions and were rel­a­tively rare.

Nearly every­thing about that will be chang­ing if the OMB has its way. The peo­ple mak­ing those sorts of de­ci­sions will no longer be ex­pert staff, but po­lit­i­cal ap­pointees. Scientific merit is meant to mat­ter less than vague stan­dards like in the na­tional in­ter­est.” And the doc­u­ment states bla­tantly that any grant pro­gram would need to be aligned with ad­min­is­tra­tion poli­cies and pri­or­i­ties.”

The ad­min­is­tra­tion has been on a los­ing streak in court cases in­volv­ing its wide­spread can­cel­la­tion of grants in 2025, in part be­cause the agen­cies do­ing the ter­mi­nat­ing did­n’t fol­low any for­mal pro­ce­dure. The new rules would for­mally de­clare that agen­cies don’t need a rea­son. All grant ap­provals would in­clude lan­guage warn­ing the re­cip­i­ent that they could be can­celed at any time if the agency pro­vid­ing the fund­ing de­cides that the grant is no longer in the na­tional in­ter­est.

Grants meet the cul­ture war

The doc­u­ment makes clear what sorts of things might be con­sid­ered ad­min­is­tra­tion pri­or­i­ties and na­tional in­ter­est—and they’re largely a war on woke. For ex­am­ple, the Trump ad­min­is­tra­tion can­celed PEPFAR, a pro­gram meant to limit the spread of HIV in Africa; it’s a step that is es­ti­mated to lead to hun­dreds of thou­sands of deaths. But to the OMB, that’s a good thing, be­cause the al­ter­na­tive was woke: Far-left ac­tivists hi­jacked the crit­i­cal work done by the US President’s Emergency Plan for AIDS Relief (PEPFAR), which was es­tab­lished to re­spond to the AIDS cri­sis in Africa. Due to waste­ful spend­ing, PEPFAR be­came a left-wing for­eign aid en­ti­tle­ment that at­tempted to pro­mote abor­tion and gen­der ide­ol­ogy.”

(Its cited source for that is an ed­i­to­r­ial from the Heritage Foundation, a far-right-wing think tank.)

While it de­mands viewpoint neu­tral” be­hav­ior from every­one re­ceiv­ing money, it has no is­sues with en­gag­ing in view­point dis­crim­i­na­tion it­self. For ex­am­ple, it out­right bans any fund­ing for theories of dis­parate-im­pact li­a­bil­ity,” the idea that ap­par­ently race-neu­tral rules might have im­pacts that dif­fer based on the race of the peo­ple in­volved. Also banned: any at­tempts to com­pen­sate for the his­toric dis­crim­i­na­tion that has kept women and mi­nori­ties from hav­ing equal op­por­tu­ni­ties in so­ci­ety. That’s con­sid­ered DEI, and thus for­bid­den.

Also out: fund­ing for what it terms gender ide­ol­ogy,” which it de­fines as an ef­fort to deny the bi­o­log­i­cal re­al­ity of sex or the sex bi­nary in hu­mans.” Apparently, study­ing hu­man chro­mo­so­mal dis­or­ders, which can re­sult in un­usual com­bi­na­tions of X and Y chro­mo­somes, is no longer wel­come in the US. Ending gov­ern­ment-spon­sored pro­mo­tion of di­vi­sive gen­der ide­ol­ogy is crit­i­cal to sci­en­tific in­quiry, pub­lic safety, and trust in gov­ern­ment,” the OMB as­serts, based on no ev­i­dence what­so­ever.

There’s also a po­lit­i­cal lit­mus test for fund­ing that harkens back to the McCarthy era, when those with un-American” ideas were os­tra­cized. OMB pro­poses a new pro­vi­sion that agen­cies may con­sider an ap­pli­can­t’s af­fil­i­a­tions with or­ga­ni­za­tions en­gaged in ac­tiv­i­ties that vi­o­late Federal law, un­der­mine pub­lic safety or na­tional se­cu­rity, or ad­vo­cate for the over­throw of the United States Government,” the doc­u­ment notes.

Good luck col­lab­o­rat­ing or pub­lish­ing

These would all be prob­lem­atic on their own, but the OMB is just warm­ing up. If you had for­eign col­lab­o­ra­tors, you might be out of luck. The doc­u­ment sug­gests an out­right ban on fed­eral fund­ing of col­lab­o­ra­tions in­volv­ing Chinese re­searchers. But even our al­lies are ap­par­ently meant to be col­lab­o­rated with as a last re­sort. When de­sign­ing re­search and de­vel­op­ment pro­grams, and eval­u­at­ing ap­pli­ca­tions,” the OMB states, Federal agen­cies must ap­ply a do­mes­tic-first frame­work, un­der which in­ter­na­tional el­e­ments may be in­cluded only if the Federal agency de­ter­mines that such el­e­ments are jus­ti­fied, con­sis­tent with pro­gram ob­jec­tives, and in the na­tional in­ter­est of the United States.”

(There are some in­di­ca­tions that agen­cies started ap­ply­ing this stan­dard even be­fore the OMB doc­u­ment was pub­lished.)

Research jour­nals gen­er­ally re­quire sci­en­tists to pay for the priv­i­lege of pub­lish­ing there. But if the OMB gets its way, mak­ing these pay­ments from a grant will be for­bid­den un­less you get ap­proval from the fund­ing agency: OMB is re­vis­ing the sec­tion to make pub­li­ca­tion costs un­al­low­able un­less such costs are ex­pressly re­quired by statute or ap­proved in ad­vance by the Federal agency on a case-by-case ba­sis.” The same ap­proval will be needed to pay for travel to a con­fer­ence.

Amazingly, OMB is cre­at­ing this mas­sive ad­min­is­tra­tive has­sle in a doc­u­ment that claims it is reducing re­cip­i­ent bur­den.” Its jus­ti­fi­ca­tion for that claim is that it’s elim­i­nat­ing any DEI re­quire­ments.

If you wanted to crip­ple sci­ence re­search and were dis­ap­pointed that Congress con­tin­ued to fund it, this is the sort of doc­u­ment you would pro­duce. It pulls US sci­en­tists out of the in­ter­na­tional com­mu­nity, leaves them un­able to com­mu­ni­cate their find­ings and meet with other sci­en­tists, and leaves grant ap­pli­ca­tions sub­ject to cul­ture war lit­mus tests and the whims of non-ex­pert bu­reau­crats. Those lucky enough to see a grant funded will live in con­stant fear that it could be can­celed when­ever the winds change in Washington, DC.

Public com­ment on the pro­posed rule is now open.

John is Ars Technica’s sci­ence ed­i­tor. He has a Bachelor of Arts in Biochemistry from Columbia University, and a Ph.D. in Molecular and Cell Biology from the University of California, Berkeley. When phys­i­cally sep­a­rated from his key­board, he tends to seek out a bi­cy­cle, or a scenic lo­ca­tion for com­muning with his hik­ing boots.

173 Comments

Devlog ⚡ Zig Programming Language

ziglang.org

This page con­tains a cu­rated list of re­cent changes to main branch Zig.

This page con­tains en­tries for the year 2026. Other years are avail­able in the Devlog archive page.

May 30, 2026

ELF Linker Improvements

Author: Matthew Lugg

I’ve spent the past few weeks work­ing on our new ELF linker which de­buted in Zig 0.16.0. At the time of the 0.16.0 re­lease, this linker im­ple­men­ta­tion was in its fairly early stages, and only re­ally sup­ported link­ing Zig-only code with­out any ex­ter­nal li­braries (even libc)—hence why it was (and still is) dis­abled by de­fault (it can be en­abled with -fnew-linker). However, quite a lot of progress has been made since that ini­tial re­lease!

Here’s a nice mile­stone—as of my lat­est PR, the new ELF linker is ca­pa­ble of build­ing the self-hosted Zig com­piler with LLVM and LLD li­braries en­abled, a task which re­quires quite a few fea­tures un­der the hood.

[mlugg@nebula mas­ter]$ # Build the Zig com­piler us­ing the new linker: [mlugg@nebula mas­ter]$ zig build -Dno-lib -Dnew-linker -Denable-llvm [mlugg@nebula mas­ter]$ # Use that com­piler to build some­thing with LLVM and LLD: [mlugg@nebula mas­ter]$ ./zig-out/bin/zig build-exe ~/hello.zig -fllvm -flld [mlugg@nebula mas­ter]$ ./hello Hello, World! [mlugg@nebula mas­ter]$

Of course, an ELF linker is­n’t nec­es­sar­ily the most ex­cit­ing thing in the world, which is why the head­line fea­ture of this new linker is its sup­port for fast in­cre­men­tal com­pi­la­tion. After the re­cent en­hance­ments, it is now pos­si­ble (on x86_64 Linux) to per­form in­cre­men­tal re­builds while link­ing ex­ter­nal li­braries, C sources, etc—with­out any ad­di­tional per­for­mance over­head! Here’s a clip of me try­ing it out on Andrew’s Tetris clone:

Oh, and fast in­cre­men­tal re­builds also work nicely on the Zig com­piler it­self:

[mlugg@nebula mas­ter]$ zig build -Dno-lib -Denable-llvm -fincremental –watch Build Summary: 4/4 steps suc­ceeded in­stall suc­cess └─ in­stall zig suc­cess └─ com­pile exe zig Debug na­tive suc­cess 36s

Build Summary: 4/4 steps suc­ceeded in­stall suc­cess └─ in­stall zig suc­cess └─ com­pile exe zig Debug na­tive suc­cess 244ms

Build Summary: 4/4 steps suc­ceeded in­stall suc­cess └─ in­stall zig suc­cess └─ com­pile exe zig Debug na­tive suc­cess 228ms

Build Summary: 4/4 steps suc­ceeded in­stall suc­cess └─ in­stall zig suc­cess └─ com­pile exe zig Debug na­tive suc­cess 288ms

Build Summary: 4/4 steps suc­ceeded in­stall suc­cess └─ in­stall zig suc­cess └─ com­pile exe zig Debug na­tive suc­cess 283ms

The biggest miss­ing fea­ture of this linker im­ple­men­ta­tion right now is that it still does not yet sup­port gen­er­at­ing DWARF de­bug in­for­ma­tion for Zig code—that’s def­i­nitely my next pri­or­ity. But even with­out that sup­port, it’s amaz­ing just how use­ful in­stant re­builds can be, for ex­am­ple in any sit­u­a­tion where you’re do­ing a lot of print de­bug­ging.

If you’re us­ing the mas­ter branch of Zig and you’re on x86_64 Linux, con­sider try­ing out in­cre­men­tal com­pi­la­tion with the new ELF linker if it pre­vi­ously was­n’t work­ing with your pro­ject! I ex­pect many code­bases to al­ready work great with it, un­lock­ing the abil­ity to re­build your pro­ject in mil­lisec­onds. Of course, if you come across any bugs, please do open an is­sue.

And if you’re cur­rently stick­ing to tagged re­leases of Zig, don’t worry—as Andrew men­tioned in his last de­vlog, Zig 0.17.0 is just around the cor­ner, so it won’t be long be­fore you can try this too!

May 26, 2026

Build System Reworked

Author: Andrew Kelley

Big branch just landed: sep­a­rate the maker process from the con­fig­urer process

This de­vlog en­try is es­sen­tially a pre­view of the up­com­ing re­lease notes, but serves as an ad­vanced no­tice to those who want to help test out the new fea­tures and pro­vide feed­back that will guide the Zig pro­ject mov­ing for­ward.

Before, build.zig files plus the build sys­tem im­ple­men­ta­tion were all com­piled into one bloated process, in Debug mode. After build.zig logic fin­ished con­struct­ing a build graph in mem­ory, the build run­ner” code ex­e­cuted it.

Now, build.zig files are com­piled into a small process (the configurer”) in de­bug mode. After this logic fin­ishes con­struct­ing a build graph in mem­ory, it is se­ri­al­ized to a bi­nary con­fig­u­ra­tion file. The par­ent zig build process is aware of this file and caches it for next time. While wait­ing for all that, it asyn­chro­nously com­piles the build graph ex­e­cu­tion process (the maker”) in re­lease mode. Once the con­fig­u­ra­tion file is avail­able and the maker process is fin­ished com­pil­ing, the maker process is ex­e­cuted, pass­ing it the con­fig­u­ra­tion file. The maker process only needs to be com­piled once per zig ver­sion thanks to the global cache. The maker process then ex­e­cutes the build graph, which is con­tained within the se­ri­al­ized con­fig­u­ra­tion file.

The pri­mary mo­ti­va­tion of this change was to make zig build faster, in three ways:

Only the user’s build.zig logic will be com­piled with each change, rather than the en­tire build sys­tem along with it. This is start­ing to be­come more valu­able now that we have in­tro­duced –watch, –fuzz and –webui. The build sys­tem can grow more fea­tures with­out mak­ing zig build take longer.

Only the user’s build.zig logic will be com­piled with each change, rather than the en­tire build sys­tem along with it. This is start­ing to be­come more valu­able now that we have in­tro­duced –watch, –fuzz and –webui. The build sys­tem can grow more fea­tures with­out mak­ing zig build take longer.

Now the build sys­tem can skip re­run­ning the build.zig logic en­tirely when it knows noth­ing will change, for ex­am­ple if you add -freference-trace to your zig build com­mand line, it now avoids re-run­ning your build.zig logic re­dun­dantly, us­ing the same con­fig­u­ra­tion as last time.

Now the build sys­tem can skip re­run­ning the build.zig logic en­tirely when it knows noth­ing will change, for ex­am­ple if you add -freference-trace to your zig build com­mand line, it now avoids re-run­ning your build.zig logic re­dun­dantly, us­ing the same con­fig­u­ra­tion as last time.

Now the process that ac­tu­ally ex­e­cutes the build graph is com­piled with op­ti­miza­tions en­abled.

Now the process that ac­tu­ally ex­e­cutes the build graph is com­piled with op­ti­miza­tions en­abled.

To demon­strate points 2 and 3, here is the dif­fer­ence be­tween run­ning zig build –help be­fore and af­ter:

Benchmark 1 (34 runs): mas­ter/​zig build -h mea­sure­ment mean ± σ min … max out­liers delta wal­l_­time 150ms ± 5.52ms 145ms … 165ms 4 (12%) 0% peak_rss 84.8MB ± 275KB 84.2MB … 85.1MB 0 ( 0%) 0% cpu_­cy­cles 593M ± 4.01M 588M608M 2 ( 6%) 0% in­struc­tions 995M ± 52.5K 995M995M 0 ( 0%) 0% cache_ref­er­ences 25.8M ± 165K 25.4M … 26.1M 0 ( 0%) 0% cache_misses 651K ± 20.1K 619K697K 0 ( 0%) 0% branch_misses 918K ± 7.44K 906K935K 0 ( 0%) 0% Benchmark 2 (348 runs): branch/​zig build -h mea­sure­ment mean ± σ min … max out­liers delta wal­l_­time 14.3ms ± 744us 13.2ms … 23.3ms 8 ( 2%) ⚡- 90.4% ± 0.4% peak_rss 78.5MB ± 562KB 77.1MB … 81.4MB 7 ( 2%) ⚡- 7.4% ± 0.2% cpu_­cy­cles 24.1M ± 821K 22.8M … 27.1M 3 ( 1%) ⚡- 95.9% ± 0.1% in­struc­tions 43.7M ± 23.8K 43.7M … 43.8M 56 (16%) ⚡- 95.6% ± 0.0% cache_ref­er­ences 1.46M ± 14.6K 1.40M … 1.50M 19 ( 5%) ⚡- 94.3% ± 0.1% cache_misses 142K ± 4.87K 127K157K 2 ( 1%) ⚡- 78.1% ± 0.4% branch_misses 126K ± 1.37K 120K129K 12 ( 3%) ⚡- 86.3% ± 0.1%

It’s dra­matic be­cause be­fore, build.zig logic was be­ing ex­e­cuted with each zig build com­mand, but now, the build sys­tem uses the cached, se­ri­al­ized con­fig­u­ra­tion in­stead.

Aside from per­for­mance, I ex­pect third-party tool­ing such as ZLS to ben­e­fit from con­sum­ing the se­ri­al­ized con­fig­u­ra­tion file rather than main­tain­ing a fork of the build run­ner.

This change­set heav­ily re­works the in­ter­nal mech­a­nism of the zig build sys­tem, how­ever, it is mostly non-break­ing from an API per­spec­tive, with the ex­cep­tions noted in the PR linked above.

For most peo­ple I’m guess­ing this is the main break­ing change they’ll hit:

if (b.args) |args| { run_cmd.ad­dArgs(args); }

⬇️

run_cmd.ad­dPassthru­Args();

This re­moves a ca­pa­bil­ity from build scripts since they can no longer ob­serve those ar­gu­ments. In ex­change, it means that when chang­ing those ar­gu­ments, build scripts no longer must be re­built from source.

If you’re some­one who wants to in­flu­ence the di­rec­tion of Zig, this is a good time to up­grade your pro­jects to the de­vel­op­ment ver­sion and try out these changes. We’ll be re­leas­ing 0.17.0 within a cou­ple weeks from now. However, if you don’t have time, and you find out that 0.17.0 broke your build, don’t worry, there will be plenty of op­por­tu­nity to get fixes in for the 0.17.1 tag as well.

April 08, 2026

Incremental com­pi­la­tion with LLVM

Author: Matthew Lugg

I’ve been spend­ing a bit of time work­ing on per­sonal pro­jects af­ter merg­ing my type res­o­lu­tion changes last month, but I did find the time re­cently to make some im­prove­ments to the LLVM code­gen back­end. This in­volved a few dif­fer­ent en­hance­ments with var­i­ous goals, but one nice user-fac­ing change was that I man­aged to get in­cre­men­tal com­pi­la­tion work­ing with the LLVM back­end.

Sadly this can’t do any­thing to speed up the dreaded LLVM Emit Object: that time is en­tirely down to LLVM. However, what in­cre­men­tal com­pi­la­tion does help with is min­i­miz­ing the time spent in the ac­tual Zig com­piler code, which means that if your code has com­pile er­rors (so LLVM Emit Object” will be skipped), you’ll usu­ally get those er­rors very quickly. (Of course, it does still give you a slight speed-up in suc­cess­ful builds too.)

This sup­port is avail­able in mas­ter branch builds right now, and will be in the 0.16.0 re­lease (which we’ll be tag­ging very soon).

For any­one who still has­n’t tried it, es­pe­cially if you’re us­ing Zig’s mas­ter branch, please do try out in­cre­men­tal com­pi­la­tion by pass­ing -fincremental –watch to zig build! The Zig core team have ben­e­fited from in­cre­men­tal com­pi­la­tion in our work­flows for a good year now, and we’re also hear­ing good things from users. The fea­ture is rel­a­tively sta­ble at this point, and peo­ple are of­ten sur­prised how much time they can save just by get­ting up-to-date com­pile er­rors in mil­lisec­onds rather than sec­onds.

I haven’t re­ally per­son­ally used in­cre­men­tal com­pi­la­tion with the LLVM back­end, but all of the in­cre­men­tal test cov­er­age in CI is now en­abled for the LLVM back­end, and I’ve had pos­i­tive feed­back from users, so it’s def­i­nitely worth giv­ing a shot. As al­ways, if you en­counter bugs in in­cre­men­tal com­pi­la­tion, please re­port them if you can!

Thank you, and I hope you find this use­ful :)

March 10, 2026

Type res­o­lu­tion re­design, with lan­guage changes to taste

Author: Matthew Lugg

Today, I merged a 30,000 line PR af­ter two (arguably three) months of work. The goal of this branch was to re­work the Zig com­pil­er’s in­ter­nal type res­o­lu­tion logic to a more log­i­cal and straight­for­ward de­sign. It’s a quite ex­cit­ing change for me per­son­ally, be­cause it al­lowed me to clean up a bunch of the com­piler guts, but it also has some nice user-fac­ing changes which you might be in­ter­ested in!

For one thing, the Zig com­piler is now lazier about an­a­lyz­ing the fields of types: if the type is never ini­tial­ized, then there’s no need for Zig to care what that type looks like”. This is im­por­tant when you have a type which dou­bles as a name­space, a com­mon pat­tern in mod­ern Zig. For in­stance, when us­ing std.Io.Writer, you don’t want the com­piler to also pull in a bunch of code in std.Io! Here’s a straight­for­ward ex­am­ple:

const Foo = struct { bad_­field: @compileError(“i am an evil field, mua­haha”), const some­thing = 123; }; comp­time { _ = Foo.something; // `Foo` only used as a name­space }

Previously, this code emit­ted a com­pile er­ror. Now, it com­piles just fine, be­cause Zig never ac­tu­ally looks at the @compileError call.

Another im­prove­ment we’ve made is in the dependency loop” ex­pe­ri­ence. Anyone who has en­coun­tered a de­pen­dency loop com­pile er­ror in Zig be­fore knows that the er­ror mes­sages for them are en­tirely un­help­ful—but that’s now changed! If you en­counter one (which is also a bit less likely now than it used to be), you’ll get a de­tailed er­ror mes­sage telling you ex­actly where the de­pen­dency loop comes from. Check it out:

const Foo = struct { in­ner: Bar }; const Bar = struct { x: u32 align(@alig­nOf(Foo)) }; comp­time { _ = @as(Foo, un­de­fined); }

$ zig build-obj re­pro.zig er­ror: de­pen­dency loop with length 2 re­pro.zig:1:29: note: type repro.Foo’ de­pends on type repro.Bar’ for field de­clared here const Foo = struct { in­ner: Bar }; ^~~ re­pro.zig:2:44: note: type repro.Bar’ de­pends on type repro.Foo’ for align­ment query here const Bar = struct { x: u32 align(@alig­nOf(Foo)) }; ^~~ note: elim­i­nate any one of these de­pen­den­cies to break the loop

Of course, de­pen­dency loops can get much more com­pli­cated than this, but in every case I’ve tested, the er­ror mes­sage has had enough in­for­ma­tion to eas­ily see what’s go­ing on.

Additionally, this PR made big im­prove­ments to the Zig com­pil­er’s incremental com­pi­la­tion” fea­ture. The short ver­sion is that it fixed a huge amount of known bugs, but in par­tic­u­lar, over-analysis” prob­lems (where an in­cre­men­tal up­date did more work than should be nec­es­sary, some­times by a big mar­gin) should fi­nally be all but elim­i­nated—mak­ing in­cre­men­tal com­pi­la­tion sig­nif­i­cantly faster in many cases! If you’ve not al­ready, con­sider try­ing out in­cre­men­tal com­pi­la­tion: it re­ally is a lovely de­vel­op­ment ex­pe­ri­ence. This is for sure the im­prove­ment which ex­cites me the most, and a large part of what mo­ti­vated this change to be­gin with.

There are a bunch more changes that come with this PR—dozens of bug­fixes, some small lan­guage changes (mostly fairly niche), and com­piler per­for­mance im­prove­ments. It’s far too much to list here, but if you’re in­ter­ested in read­ing more about it, you can take a look at the PR on Codeberg—and of course, if you en­counter any bugs, please do open an is­sue. Happy hack­ing!

February 13, 2026

io_ur­ing and Grand Central Dispatch std.Io im­ple­men­ta­tions landed

Author: Andrew Kelley

As we ap­proach the end of the 0.16.0 re­lease cy­cle, Jacob has been hard at work, bring­ing std.Io.Evented up to speed with all the lat­est API changes:

io_ur­ing im­ple­men­ta­tion

Grand Central Dispatch im­ple­men­ta­tion

Both of these are based on user­space stack switch­ing, some­times called fibers”, stackful corou­tines”, or green threads”.

They are now avail­able to tin­ker with, by con­struct­ing one’s ap­pli­ca­tion us­ing std.Io.Evented. They should be con­sid­ered ex­per­i­men­tal be­cause there is im­por­tant fol­lowup work to be done be­fore they can be used re­li­ably and ro­bustly:

bet­ter er­ror han­dling

re­move the log­ging

di­ag­nose the un­ex­pected per­for­mance degra­da­tion when us­ing IoMode.evented for the com­piler

a cou­ple func­tions still unim­ple­mented

more test cov­er­age is needed

builtin func­tion to tell you the max­i­mum stack size of a given func­tion to make these im­ple­men­ta­tions prac­ti­cal to use when over­com­mit is off.

With those caveats in mind, it seems we are in­deed reach­ing the Promised Land, where Zig code can have Io im­ple­men­ta­tions ef­fort­lessly swapped out:

const std = @import(“std”);

pub fn main(init: std.process.Init.Min­i­mal) !void { var de­bug_al­lo­ca­tor: std.heap.De­bugAl­lo­ca­tor(.{}) = .init; const gpa = de­bug_al­lo­ca­tor.al­lo­ca­tor();

var threaded: std.Io.Threaded = .init(gpa, .{ .argv0 = .init(init.args), .environ = init.en­v­i­ron, }); de­fer threaded.deinit(); const io = threaded.io();

re­turn app(io); }

fn app(io: std.Io) !void { try std.Io.File.std­out().writeStreamin­gAll(io, Hello, World!\n”); }

$ strace ./hello_threaded ex­ecve(”./​hel­lo_threaded”, [”./hello_threaded”], 0x7ffc1da88b20 /* 98 vars */) = 0 mmap(NULL, 262207, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f583f338000 arch_prctl(ARCH_SET_FS, 0x7f583f378018) = 0 prlim­it64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_­max=RLIM64_IN­FIN­ITY}) = 0 prlim­it64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_­max=RLIM64_IN­FIN­ITY}, NULL) = 0 sigalt­stack({ss_sp=0x7f583f338000, ss_flags=0, ss_­size=262144}, NULL) = 0 sched_getaffin­ity(0, 128, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]) = 8 rt_sigac­tion(SI­GIO, {sa_handler=0x1019d90, sa_­mask=[], sa_flags=SA_RE­STORER, sa_re­storer=0x10328c0}, {sa_handler=SIG_DFL, sa_­mask=[], sa_flags=0}, 8) = 0 rt_sigac­tion(SIG­PIPE, {sa_handler=0x1019d90, sa_­mask=[], sa_flags=SA_RE­STORER, sa_re­storer=0x10328c0}, {sa_handler=SIG_DFL, sa_­mask=[], sa_flags=0}, 8) = 0 writev(1, [{iov_base=“Hello, World!\n”, iov_len=14}], 1Hello, World! ) = 14 rt_sigac­tion(SI­GIO, {sa_handler=SIG_DFL, sa_­mask=[], sa_flags=SA_RE­STORER, sa_re­storer=0x10328c0}, NULL, 8) = 0 rt_sigac­tion(SIG­PIPE, {sa_handler=SIG_DFL, sa_­mask=[], sa_flags=SA_RE­STORER, sa_re­storer=0x10328c0}, NULL, 8) = 0 ex­it_­group(0) = ? +++ ex­ited with 0 +++

Swapping out only the I/O im­ple­men­ta­tion:

const std = @import(“std”);

pub fn main(init: std.process.Init.Min­i­mal) !void { var de­bug_al­lo­ca­tor: std.heap.De­bugAl­lo­ca­tor(.{}) = .init; const gpa = de­bug_al­lo­ca­tor.al­lo­ca­tor();

var evented: std.Io.Evented = un­de­fined; try evented.init(gpa, .{ .argv0 = .init(init.args), .environ = init.en­v­i­ron, .backing_allocator_needs_mutex = false, }); de­fer evented.deinit(); const io = evented.io();

re­turn app(io); }

fn app(io: std.Io) !void { try std.Io.File.std­out().writeStreamin­gAll(io, Hello, World!\n”); }

ex­ecve(”./​hel­lo_evented”, [”./hello_evented”], 0x7fff368894f0 /* 98 vars */) = 0 mmap(NULL, 262215, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4c28000 arch_prctl(ARCH_SET_FS, 0x7f70a4c68020) = 0 prlim­it64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_­max=RLIM64_IN­FIN­ITY}) = 0 prlim­it64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_­max=RLIM64_IN­FIN­ITY}, NULL) = 0 sigalt­stack({ss_sp=0x7f70a4c28008, ss_flags=0, ss_­size=262144}, NULL) = 0 sched_getaffin­ity(0, 128, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]) = 8 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4c27000 mmap(0x7f70a4c28000, 548864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4ba1000 io_ur­ing_setup(64, {flags=IORING_SETUP_COOP_TASKRUN|IORING_SETUP_SINGLE_ISSUER, sq_thread­_cpu=0, sq_thread­_i­dle=1000, sq_en­tries=64, cq_en­tries=128, fea­tures=IOR­ING_FEAT_S­IN­GLE_MMAP|IOR­ING_FEAT_N­ODROP|IOR­ING_FEAT_­SUB­MIT_STA­BLE|IOR­ING_FEAT_R­W_CUR_­POS|IOR­ING_FEAT_CUR_PER­SON­AL­ITY|IOR­ING_FEAT_­FAST_POLL|IOR­ING_FEAT_POL­L_32BITS|IOR­ING_FEAT_SQPOL­L_NON­FIXED|IOR­ING_FEAT_EX­T_ARG|IOR­ING_FEAT_­NA­TIVE_­WORK­ERS|IOR­ING_FEAT_RSR­C_­TAGS|IOR­ING_FEAT_C­QE_SKIP|IOR­ING_FEAT_LINKED_­FILE|IOR­ING_FEAT_REG_REG_RING|IOR­ING_FEAT_RECVSEND_BUN­DLE|IOR­ING_FEAT_MIN_­TIME­OUT|IOR­ING_FEAT_R­W_ATTR|IOR­ING_FEAT_NO_IOWAIT, sq_off={head=0, tail=4, ring_­mask=16, ring_en­tries=24, flags=36, dropped=32, ar­ray=2112, user_addr=0}, cq_off={head=8, tail=12, ring_­mask=20, ring_en­tries=28, over­flow=44, cqes=64, flags=40, user_addr=0}}) = 3 mmap(NULL, 2368, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, 3, 0) = 0x7f70a4ba0000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, 3, 0x10000000) = 0x7f70a4b9f000 io_ur­ing_en­ter(3, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8Hello, World! ) = 1 io_ur­ing_en­ter(3, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1 mun­map(0x7f70a4b9f000, 4096) = 0 mun­map(0x7f70a4ba0000, 2368) = 0 close(3) = 0 mun­map(0x7f70a4ba1000, 548864) = 0 ex­it_­group(0) = ? +++ ex­ited with 0 +++

Key point here be­ing that the app func­tion is iden­ti­cal be­tween those two snip­pets.

Moving be­yond Hello World, the Zig com­piler it­self works fine us­ing std.Io.Evented, both with io_ur­ing and with GCD, but as men­tioned above, there is a not-yet-di­ag­nosed per­for­mance degra­da­tion when do­ing so.

Happy hack­ing,

Andrew

February 06, 2026

Two Package Management Workflow Enhancements

Author: Andrew Kelley

GitHub - kristapsdz/openrsync: BSD-licensed implementation of rsync

github.com

Introduction

This sys­tem has been merged into OpenBSD base. If you’d like to con­tribute to openr­sync, please mail your patches to tech@openbsd.org. This repos­i­tory is sim­ply the OpenBSD ver­sion plus some glue for porta­bil­ity.

This is an im­ple­men­ta­tion of rsync with a BSD (ISC) li­cense. It’s com­pat­i­ble with a mod­ern rsync (3.1.3 is used for test­ing, but any sup­port­ing pro­to­col 27 will do), but ac­cepts only a sub­set of rsync’s com­mand-line ar­gu­ments.

Its of­fi­cially-sup­ported op­er­at­ing sys­tem is OpenBSD, but it will com­pile and run on other UNIX sys­tems. See Portability for de­tails.

The canon­i­cal doc­u­men­ta­tion for openr­sync is its man­ual pages. See rsync(5) and rsyncd(5) for pro­to­col de­tails or util­ity doc­u­men­ta­tion in openr­sync(1). If you’d like to write your own rsync im­ple­men­ta­tion, the pro­to­col man­pages should have all the in­for­ma­tion re­quired.

The Architecture and Algorithm sec­tions on this page serve to in­tro­duce de­vel­op­ers to the source code. They are non-canon­i­cal.

Project back­ground

openr­sync is writ­ten as part of the rpki-client(1) pro­ject, an RPKI val­ida­tor for OpenBSD. openr­sync was funded by NetNod, IIS.SE, SUNET and 6connect.

Installation

On an up-to-date UNIX sys­tem, sim­ply down­load and run:

% ./configure % make # make in­stall

This will in­stall the openr­sync util­ity and man­ual pages. It’s ok to have an in­stal­la­tion of rsync at the same time: the two will not col­lide in any way.

If you up­grade your sources and want to re-in­stall, just run the same. If you’d like to unin­stall the sources:

# make unin­stall

If you’d like to in­ter­act with the openr­sync as a server, you can run the fol­low­ing:

% rsync –rsync-path=openrsync src/* dst % openr­sync –rsync-path=openrsync src/* dst

If you’d like openr­sync and rsync to in­ter­act, it’s im­por­tant to use com­mand-line flags avail­able on both. See openr­sync(1) for a list­ing.

Algorithm

For a ro­bust de­scrip­tion of the rsync al­go­rithm, see The rsync al­go­rithm”, by Andrew Tridgell and Paul Mackerras. Andrew Tridgell’s PhD the­sis, Efficient Algorithms for Sorting and Synchronization”, cov­ers the top­ics in more de­tail. This gives a de­scrip­tion suit­able for delv­ing into the source code.

The rsync al­go­rithm has two com­po­nents: the sender and the re­ceiver. The sender man­ages source files; the re­ceiver man­ages the des­ti­na­tion. In the fol­low­ing in­vo­ca­tion, first the sender is host re­mote and the re­ceiver is the lo­cal­host, then the op­po­site.

% openr­sync -lrtp re­mote:foo/​bar ~/baz/xyzzy % openr­sync -lrtp ~/foo/bar re­mote:baz/​xyzzy

The al­go­rithm hinges upon a file list of names and meta­data (e.g., mode, mtime, etc.) shared be­tween com­po­nents. The file list de­scribes all source files of the up­date and is gen­er­ated by the sender. The shar­ing is im­ple­mented in flist.c.

After shar­ing this list, both the re­ceiver and sender in­de­pen­dently sort the en­tries by the file­names’ lex­i­co­graph­i­cal or­der. This al­lows the file list to be sent and re­ceived out of or­der. The or­der­ing pre­serves a di­rec­tory-first or­der, so di­rec­to­ries are processed be­fore their con­tained files. Moreover, once sorted, both sender and re­ceiver may re­fer to file en­tries by their po­si­tion in the sorted ar­ray.

After the re­ceiver reads the list, it it­er­ates through each file in the list, pass­ing in­for­ma­tion to the sender so that the sender may send back in­struc­tions to up­date the file. This is called the block ex­change” and is the maintstay of the rsync al­go­rithm. During the block ex­change, the sender waits to re­ceive a re­quest for up­date or end of se­quence mes­sage; once a re­quest is re­ceived, it scans for new blocks to send to the re­ceiver.

Once the block ex­change is com­plete, the files are all up to date.

The re­ceiver is im­ple­mented in re­ceiver.c; the sender, in sender.c. A great deal of the block ex­change hap­pens in blocks.c.

Block ex­change

The block ex­change se­quence is dif­fer­ent for whether the file is a di­rec­tory, sym­bolic link, or reg­u­lar file.

For sym­bolic links, the in­for­ma­tion re­quired by the re­ceiver is al­ready en­coded in the file list meta­data. The sym­bolic link is up­dated to point to the cor­rect tar­get. No up­date is re­quested from the sender.

For di­rec­to­ries, the di­rec­tory is cre­ated if it does not al­ready ex­ist. No up­date is re­quested from the sender.

Regular files are han­dled as fol­lows. First, the file is checked to see if it’s up to date. This hap­pens if the file size and last mod­i­fi­ca­tion time are the same. If so, no up­date is re­quested from the sender.

Otherwise, the re­ceiver ex­am­ines each file in blocks of a fixed size. See Block sizes for de­tails. (The ter­mi­nal block may be smaller if the file size is not di­vis­i­ble by the block size.) If the file is empty or does not ex­ist, it will have zero blocks. Each block is hashed twice: first, with a fast Adler-32 type 4-byte hash; sec­ond, with a slower MD4 16-byte hash. These hashes are im­ple­mented in hash.c. The re­ceiver sends the file’s block hashes to the sender.

Once ac­cepted, the sender ex­am­ines the cor­re­spond­ing file with the given blocks. For each byte in the source file, the sender com­putes a fast hash given the block size. It then looks for match­ing fast hashes in the sent block in­for­ma­tion. If it finds a match, it then com­putes and checks the slow hash. If no match is found, it con­tin­ues to the next byte. The match­ing (and in­deed all block op­er­a­tion) is im­ple­mented in block.c.

When a match is found, the data prior to the match is first sent as a stream of bytes to the re­ceiver. This is fol­lowed by an iden­ti­fier for the found block, or zero if no more data is forth­com­ing.

The re­ceiver writes the stream of bytes first, then copies the data in the iden­ti­fied block if one has been spec­i­fied. This con­tin­ues un­til the end of file, at which point the file has been fully re­con­sti­tuted.

If the file does not ex­ist on the re­ceiver side–-the ba­sis case–-the en­tire file is sent as a stream of bytes.

Following this, the whole file is hashed us­ing an MD4 hash. These hashes are then com­pared; and on suc­cess, the al­go­rithm con­tin­ues to the next file.

Block sizes

The block size al­go­rithm plays a cru­cial role in the pro­to­col ef­fi­ciency. In gen­eral, the block size is the rounded square root of the to­tal file size. The min­i­mum block size, how­ever, is 700 B. Otherwise, the square root com­pu­ta­tion is sim­ply sqrt(3) fol­lowed by ceil(3)

For rea­sons un­known, the square root re­sult is rounded up to the near­est mul­ti­ple of eight.

Architecture

Each openr­sync ses­sion is di­vided into a run­ning server and client process. The client openr­sync process is ex­e­cuted by the user.

% openr­sync -rlpt host:path/​to/​source dest

The server openr­sync is ex­e­cuted on a re­mote host ei­ther on-de­mand over ssh(1) or as a per­sis­tent net­work dae­mon. If ex­e­cuted over ssh(1), the server openr­sync is dis­tin­guished from a client (user-started) openr­sync by the –server flag.

Once the client or server openr­sync process starts, it ex­am­ines the com­mand-line ar­gu­ments to de­ter­mine whether it’s in re­ceiver or sender mode. (The dae­mon is sent the com­mand-line ar­gu­ments in a pro­to­col-spe­cific way de­scribed in rsyncd(5), but oth­er­wise does the same thing.) The re­ceiver is the des­ti­na­tion for files; the sender is the ori­gin. There is al­ways one re­ceiver and one sender.

The server process is ex­plic­itly in­structed that it is a sender with the –sender com­mand-line flag, oth­er­wise it is a re­ceiver. The client process im­plic­itly de­ter­mines its sta­tus by look­ing at the files passed on the com­mand line for whether they are lo­cal or re­mote.

openr­sync path/​to/​source host:des­ti­na­tion openr­sync host:source path/​to/​des­ti­na­tion

In the first ex­am­ple, the client is the sender: it sends data from it­self to the server. In the sec­ond, the op­po­site is true in that it re­ceives data.

The clien­t’s com­mand-line files may have any of the fol­low­ing host spec­i­fi­ca­tions that de­ter­mine lo­cal­ity.

lo­cal: ../path/to/source ../another

re­mote server: host:path/​to/​source :path/to/another

re­mote dae­mon: rsync://​host/​mod­ule/​path ::another

Host spec­i­fi­ca­tions must be con­sis­tent: sources must all be lo­cal or all be re­mote on the same host. Both may not be re­mote. (Aside: it’s tech­ni­cally pos­si­ble to do this. I’m not sure why the GPL rsync is lim­ited to one or the other.)

If the source or des­ti­na­tion is on a re­mote server, the client then fork(2)s and starts the server openr­sync on the re­mote host over ssh(1). The client and the server sub­se­quently com­mu­ni­cate over sock­et­pair(2) pipes. If on a re­mote dae­mon, the client does not fork, but in­stead con­nects to the stand­alone server with a net­work socket(2).

The server’s com­mand-line, whether passed to an openr­sync spawned on-de­mand over an ssh(1) ses­sion or passed to the dae­mon, dif­fers from the clien­t’s.

openr­sync –server [–sender] . files…

The files given are ei­ther the sin­gle des­ti­na­tion di­rec­tory when in re­ceiver mode, or the list of sources when in sender mode. The stand­alone full-stop is a mys­tery to me.

Locality de­tec­tion and rout­ing to client and server run-times are han­dled in main.c. The client for a server is im­ple­mented in client.c and the server in server.c. The client for a net­work dae­mon is in socket.c. Invocation of the re­mote server openr­sync is man­aged in child.c.

Once the client and server be­gin, they start to ne­go­ti­ate the trans­fer of files over the con­nected socket. The pro­to­col used is spec­i­fied in rsync(5). For dae­mon con­nec­tions, the rsyncd(5) pro­to­col is also used for hand­shak­ing.

The re­ceiver side is man­aged in re­ceiver.c and the sender in sender.c.

The re­ceiver side tech­ni­cally has two func­tions: not only must it up­load block meta­data to the sender, it must also han­dle data writes as they are sent by the sender. The rsync pro­to­col is de­signed so that the sender re­ceives block re­quests and con­tin­u­ously sends data to the re­ceiver.

To ac­com­plish this, the re­ceiver mul­ti­tasks as the up­loader and down­loader. These roles are im­ple­mented in up­loader.c. and down­loader.c, re­spec­tively. The mul­ti­task­ing takes place by a fi­nite state ma­chine dri­ven by data com­ing from the sender and files on disc are they are ready to be check­summed and up­loaded.

The up­loader scans through the list of files and asyn­chro­nously opens files to process blocks. While it waits for the files to open, it re­lin­quishes con­trol to the event loop. When files are avail­able, it hashes and check­sums blocks and up­loads to the sender.

The down­loader waits on data from the sender. When data is ready (and pre­fixed by the file it will up­date), the down­loader asyn­chro­nously opens the ex­ist­ing file to per­form any block copy­ing. When the file is avail­able for read­ing, it then con­tin­ues to read data from the sender and copy from the ex­ist­ing file.

Differences from rsync

The de­sign of rsync in­volves an­other mode run­ning along­side the re­ceiver: the gen­er­a­tor. This is im­ple­mented as an­other process fork(2)ed from the re­ceiver, and com­mu­ni­cat­ing with the re­ceiver and sender.

In openr­sync, the gen­er­a­tor and re­ceiver are one process, and an event loop is used for speedy re­sponses to read and write re­quests.

Security

Besides the usual de­fen­sive pro­gram­ming, openr­sync makes sig­nif­i­cant use of na­tive se­cu­rity fea­tures.

The sys­tem op­er­a­tions avail­able to ex­e­cut­ing code are fore­most lim­ited by OpenBSD’s pledge(2). The pledges given de­pend upon the op­er­at­ing mode. For ex­am­ple, the re­ceiver needs write ac­cess to the disc–-but only when not in dry-run mode (-n). The dae­mon client needs DNS and net­work ac­cess, but only to a point. pledge(2) al­lows avail­able re­sources to be lim­ited over the course of op­er­a­tion.

The sec­ond tool is OpenBSD’s un­veil(2), which lim­its ac­cess to the file-sys­tem. This pro­tects against rogue at­tempts to break out” of the des­ti­na­tion. It’s an at­trac­tive al­ter­na­tive to ch­root(2) be­cause it does­n’t re­quire root per­mis­sions to ex­e­cute.

On the re­ceiver side, the file-sys­tem is un­veil(2)ed at and be­neath the des­ti­na­tion di­rec­tory. After the cre­ation of the des­ti­na­tion di­rec­tory, only tar­gets within that di­rec­tory may be ac­cessed or mod­i­fied.

Lastly, the MD4 hashs are seeded with ar­c4ran­dom(3) in­stead of with time(3). This is only ap­plic­a­ble when run­ning openr­sync in server mode, as the server gen­er­ates the seed.

Portability

Many have asked about porta­bil­ity.

The only of­fi­cially-sup­ported op­er­at­ing sys­tem is OpenBSD, as this has con­sid­er­able se­cu­rity fea­tures. openr­sync does, how­ever, use ocon­fig­ure for com­pi­la­tion on non-OpenBSD sys­tems. This is to en­cour­age port­ing.

It cur­rently is portable across Linux (glibc and musl), FreeBSD, NetBSD, Mac OS X, and OmniOS. This is en­forced by the GitHub CI mech­a­nism, which tests on this sys­tems. Architectures tested for in­clude x86_64, aarch64, and s390x.

The ac­tual work of port­ing is match­ing the se­cu­rity fea­tures pro­vided by OpenBSD’s pledge(2) and un­veil(2). These are crit­i­cal el­e­ments to the func­tion­al­ity of the sys­tem. Without them, your sys­tem ac­cepts ar­bi­trary data from the pub­lic net­work.

This is pos­si­ble (I think?) with FreeBSD’s Capsicum, but Linux’s se­cu­rity fa­cil­i­ties are a mess, and will take an ex­pert hand to prop­erly se­cure.

rsync has spe­cific run­ning modes for the su­per-user. It also pumps ar­bi­trary data from the net­work onto your file-sys­tem. openr­sync is about 10 000 lines of C code: do you trust me not to make mis­takes?

Snowboard Kids 2 is 100% Decompiled

blog.chrislewis.au

I’m very pleased to an­nounce that Snowboard Kids 2 is 100% de­com­piled!

All of the game’s func­tions have now been im­ple­mented in C and com­pile to as­sem­bly that matches the orig­i­nal game. There’s still some oc­ca­sional __asm__ hack­ery,1 and plenty of code needs bet­ter names and doc­u­men­ta­tion, but every func­tion now has a match­ing C im­ple­men­ta­tion.

That mat­ters be­cause a match­ing de­com­pi­la­tion turns the game from a pile of MIPS as­sem­bly into a code­base we can read, build, study, and mod­ify. It should help with re­com­pi­la­tion, as­set ex­trac­tion, mod­ding, and gen­er­ally un­der­stand­ing the me­chan­ics of the N64s great­est game.

Snowboard Kids 2 de­com­pi­la­tion re­port from de­comp.dev. Boxes rep­re­sent dif­fer­ent files.

The jour­ney

This pro­ject has been a lit­tle un­der two years in the mak­ing, with the first com­mit land­ing in September 2024.

The cir­cum­stances sur­round­ing the fi­nal matches were not quite what I ex­pected when I started. I’m cur­rently sit­ting in hos­pi­tal with my new­born daugh­ter. She’s do­ing fine, but needs some help eat­ing. Decompilation has been a use­ful dis­trac­tion and an en­joy­able way to fill the quiet hours.

The path to de­com­pil­ing any game, let alone a Nintendo 64 game, is not es­pe­cially well doc­u­mented. This pro­ject would not have been pos­si­ble with­out the N64 de­com­pi­la­tion Discord com­mu­nity, whose mem­bers have been in­cred­i­bly gen­er­ous with their time. I would par­tic­u­larly like to thank Bl00D4NGEL, in­spec­tredc, SlaveOfIDO, and queueRAM for their sig­nif­i­cant con­tri­bu­tions to the pro­ject, es­pe­cially across the fi­nal ten func­tions.

Leaderboard shared on dis­cord for track­ing work on the re­main­ing Snowboard Kids 2 func­tions.

The com­mu­nity was more im­por­tant than any model: peo­ple an­swered my dumb ques­tions, ex­plained tool­ing, and de­com­piled func­tions them­selves. With that said, cod­ing agents also greatly ac­cel­er­ated the de­com­pi­la­tion ef­fort, par­tic­u­larly Claude, GLM, and Codex. I don’t want to turn this into an­other AI blog post2, but I do have a cou­ple of ob­ser­va­tions:

Based on my ex­pe­ri­ence with the fi­nal ten func­tions, which were among the most dif­fi­cult, the most ef­fec­tive model ap­peared to be Codex 5.5 xhigh. Historically Claude was more ef­fec­tive, and I ex­pect this to keep chang­ing, per­haps even by the time you read this.

Frontier mod­els are now very ef­fec­tive at de­com­pi­la­tion, but this comes at a cost. GLM has prob­a­bly been the best value for money for this spe­cific kind of work. If you want to try cod­ing agents on your own de­com­pi­la­tion pro­ject but are put off by high sub­scrip­tion fees, that is where I would start.

What next?

Reaching 100% de­com­pi­la­tion was not tech­ni­cally block­ing the re­com­pi­la­tion ef­fort, but it was more in­ter­est­ing to me per­son­ally. With the de­com­pi­la­tion fin­ished, my next goal is to re­lease a high-qual­ity re­com­pi­la­tion of Snowboard Kids 2.

That’s al­ready in a pretty good state thanks to help from son­icd­cer and DarioSamo, but there are still bugs to squash be­fore I’m com­fort­able re­leas­ing it.

Screenshot from Snowboard Kids 2: Recompiled. Note the use of widescreen and ex­panded draw dis­tance. This can lead to some vi­sual quirks.

There’s also plenty of work left to do in the de­com­pi­la­tion pro­ject it­self. A 100% match does­n’t mean the source is per­fectly un­der­stood. Many func­tions still have gen­er­ated names, many struc­tures need to be cleaned up, and graph­ics/​au­dio as­sets are still mostly treated as bi­nary blobs. The pro­ject is now in a much bet­ter place for that work, but the work still needs do­ing.

Finally, I’m in­ter­ested in start­ing a Snowboard Kids 1 de­com­pi­la­tion. I think it would be very cool to have a Super Snowboard Kids’3 that com­bines both games and al­lows you to play all the orig­i­nal tracks on the sec­ond game’s more mod­ern en­gine. I have no idea how fea­si­ble that ul­ti­mately is, but it’s a fun thing to think about.

If you’ve made it this far, you prob­a­bly have an in­ter­est in de­com­pi­la­tion and Snowboard Kids 2. Take a look at the Snowboard Kids 2 de­com­pi­la­tion pro­ject. The README in­cludes a list of good first tasks.

You can also fol­low me on Bluesky for more Snowboard Kids 2 up­dates.

The pro­ject uses some tar­geted __asm__ in­struc­tions to co­erce vari­ables into par­tic­u­lar reg­is­ters, en­sure writes hap­pen at the ap­pro­pri­ate time, etc. Generally, these could be re­moved and the game would func­tion in ex­actly the same way (albeit no longer byte-for-byte match­ing). Still, ide­ally this would­n’t be needed at all, and the long-term goal is to re­move them. ↩︎

The pro­ject uses some tar­geted __asm__ in­struc­tions to co­erce vari­ables into par­tic­u­lar reg­is­ters, en­sure writes hap­pen at the ap­pro­pri­ate time, etc. Generally, these could be re­moved and the game would func­tion in ex­actly the same way (albeit no longer byte-for-byte match­ing). Still, ide­ally this would­n’t be needed at all, and the long-term goal is to re­move them. ↩︎

I have three of those al­ready if you’re in­ter­ested. ↩︎

I have three of those al­ready if you’re in­ter­ested. ↩︎

Trivia: this was ac­tu­ally the ti­tle of Snowboard Kids 2 in Japan! ↩︎

Trivia: this was ac­tu­ally the ti­tle of Snowboard Kids 2 in Japan! ↩︎

Investigation: Hallucinations in Ernst & Young Report on Loyalty Fraud | GPTZero

gptzero.me

GPTZeroInvestigations·

Exclusive

Chasing the Hallucinations

Ernst & Young (EY) Canada pub­lished a cy­ber­se­cu­rity re­port on loy­alty pro­gram safe­guards. We chased down every ci­ta­tion. Most were hal­lu­ci­nated.

View Investigation

MAY 14, 2026

Earlier this year, an en­gi­neer at GPTZero coined the term vibe cit­ing” to de­scribe the ac­ci­den­tal cre­ation of fake ref­er­ences via LLM hal­lu­ci­na­tions. It turns out that the fric­tion of cre­at­ing and check­ing ci­ta­tions is lead­ing many re­searchers, con­sul­tants, lawyers, and pub­lic of­fi­cials to em­brace the vibe (if you know what we mean).

Among the con­verts are the au­thors of a 2025 Ernst & Young re­port ti­tled Points of Attack: Uncovering Cyber Threats and Fraud in Loyalty Systems. This re­port, stuffed with fake ci­ta­tions and in­ac­cu­rate claims, is sur­fac­ing in news­pa­pers, blog posts, and AI search overviews, poi­son­ing the data that both hu­man re­searchers and AI agents rely on.

GPTZero be­gan tar­get­ing vibe ci­ta­tions with our Hallucination Check tool in 2025, which we used to fur­ther in­ves­ti­ga­tions into a gov­ern­ment pub­li­ca­tion, two dif­fer­ent Deloitte re­ports, and pres­ti­gious ma­chine learn­ing / ar­ti­fi­cial in­tel­li­gence con­fer­ences like NeurIPS and ICLR. Over the past few months we’ve set up an au­to­mated pipeline to search for vibe ci­ta­tions by find­ing and scan­ning pub­lic re­ports from ma­jor con­sult­ing firms. What we’ve found sug­gests that the vibe cit­ing epi­demic is al­ready en­demic, even among the ma­jor play­ers.

Instead of re­leas­ing our re­sults all at once, we’re go­ing to fo­cus on one re­port at a time. This ap­proach both pre­vents in­di­vid­ual ex­am­ples be­ing over­looked and al­lows us to il­lus­trate the neg­a­tive im­pacts of vibe cit­ing on re­search qual­ity and pub­lic trust.

On the menu: Ernst & Young (EY)

Ernst & Young is one of the big four” global con­sult­ing firms, pro­vid­ing ac­count­ing and con­sult­ing ser­vices to gov­ern­ments and pri­vate en­ti­ties from 150 of­fices around the world. The Canadian mem­ber firm (EY Canada) pro­vides mil­lions of dol­lars of ser­vices to the Canadian gov­ern­ment an­nu­ally.

In late 2025, EY Canada pub­lished a 44-page re­port on cy­ber se­cu­rity ti­tled Points of Attack: Uncovering Cyber Threats and Fraud in Loyalty Systems. While cred­ited to three em­ploy­ees (two part­ners and one se­nior man­ager), the doc­u­ment is a col­lage of vibe ci­ta­tions, mis­at­tri­bu­tions, fake sta­tis­tics, and AI-written text.

Why the Vibes Are Bad

EY Canada’s re­port does­n’t use foot­notes or nor­mal aca­d­e­mic ci­ta­tions. Instead, it ref­er­ences sources di­rectly in the text and/​or in­cludes them in a re­sources table (p. 41 – 43). This table pro­vides a source ti­tle, de­scrip­tion, and URL for all sources, as well as the pub­lisher and date in cer­tain cases. Almost all of the URLs are bro­ken or fake, and more than half of the ti­tles don’t cor­re­spond to real sources.

GPTZero uses a very spe­cific de­f­i­n­i­tion of be­cause of the po­ten­tial rep­u­ta­tional cost (to both us and the re­port’s au­thors) of false pos­i­tives. One of our team mem­bers man­u­ally ver­i­fied Hallucination Check’s re­sults to en­sure their ac­cu­racy.

During our pre­vi­ous analy­sis of aca­d­e­mic con­fer­ence sub­mis­sions, we found that many au­thors pri­mar­ily used AI to gen­er­ate and for­mat their ref­er­ences, re­sult­ing in pa­pers with vibed ci­ta­tions but low AI text scores over­all.

However, it’s hard to find hu­man fin­ger­prints in Points of Attack — harder, even, than find­ing a hu­man-writ­ten LinkedIn post. Not only does the text scan as AI-generated, it’s rid­dled with com­mon LLM er­rors like fake sta­tis­tics, mis­at­tri­bu­tions, and in­ter­nal con­tra­dic­tions.

1/4

EY Report, Page 4

A bold claim in the ex­ec­u­tive sum­mary

In the re­port’s Executive Summary, its au­thors claim the global loy­alty points mar­ket is $200 bil­lion, and that 30 – 50% of those points go un­used.

EY Report, Page 42

A fake Forbes ci­ta­tion

The ci­ta­tion we just looked at sup­ports the au­thor’s orig­i­nal claim of a $200 bil­lion global mar­ket.

EY Report, Page 10

A con­tra­dic­tory claim

Yet on page 10, the $200 bil­lion fig­ure is now the es­ti­mate of unre­deemed loy­alty points, not the col­lec­tive value of all points glob­ally. Since the au­thors have al­ready claimed that up to 50% of points are unre­deemed, this new sta­tis­tic re­quires a global mar­ket value of at least $400 bil­lion.

EY Report, Page 43

A sec­ond fab­ri­cated ci­ta­tion: McKinsey

A few rows down, a fab­ri­cated McKinsey & Company re­port pro­vides ev­i­dence for the lat­ter claim — $200 bil­lion as the value of unre­deemed points glob­ally. Two in­vented ci­ta­tions, two in­com­pat­i­ble num­bers.

We chased the source of this McKinsey ci­ta­tion back to an ob­scure fin­tech

blog­post

by Financial IT, which was pub­lished six months ear­lier.

1/2

Financial IT, Page 1

A sim­i­lar claim

Six months be­fore EYs re­port, a blog post on the ob­scure U.K. fin­tech mag­a­zine Financial IT claims that more than $200 bil­lion in points sit idle each year.” The lan­guage is nearly iden­ti­cal to the EY re­port.

Financial IT, Page 3

The vibes are iden­ti­cal

The blog’s sources sec­tion cites McKinsey & Company: Loyalty Economics Report (2022)” — a re­port that does not ex­ist. This fab­ri­cated ci­ta­tion ap­pears ver­ba­tim in the EY re­port’s ref­er­ence table, laun­der­ing an in­vented source from a low-qual­ity blog into a Big Four pub­li­ca­tion.

Some of the re­port’s most du­bi­ous claims weren’t even cited at all.

1/2

EY Report, Page 6

The source is at­trib­uted to Paystone

On page 6, the au­thors claim that 72% of cus­tomer loy­alty pro­grams have re­ported theft or fraud. This fact is at­trib­uted to a 2019 post by the Canadian pay­ment proces­sor Paystone.

EY Report, Page 11

Actually, the source is Forter

However, on page 11, the same sta­tis­tic is at­trib­uted to a dif­fer­ent source — the un­usu­ally-named NRF 2020 sum­mary” pub­lished by the dig­i­tal fraud pre­ven­tion com­pany Forter. Neither of these sources are in­cluded in the re­port’s ref­er­ence table. In fact, while the sta­tis­tic is ref­er­enced on both the Paystone and Forter pages, the orig­i­nal source seems to be a 2017 sur­vey by Ipsos.

Contradicting ref­er­ences, low-qual­ity sources, and out-of-date sta­tis­tics are all in­di­ca­tions of AI slop.

1/2

EY Report, Page 6

The 89% claim

On page 6, the au­thors claim that loy­alty pro­gram fraud at­tacks have in­creased 89% since 2019.

EY Report, Page 11

A spe­cific source for this claim

Yet on page 11, this 89% in­crease is lim­ited to a sin­gle year, 2018 to 2019, and the sta­tis­tic is at­trib­uted to a spe­cific source: the Forter Fraud Attack Index. Surprisingly, this source both ex­ists and par­tially con­firms the sec­ond ver­sion of the claim. However, like many of the sources used in the EY re­port, it is sub­stan­tially out of date. Poorly para­phrased sta­tis­tics are also a sign of AI slop.

Why Vibes Matter

It’s dif­fi­cult to mea­sure the pub­lic im­pact of EYs re­port. Points of Attack seems to have made few waves in Canada; how­ever, it was re­cently ref­er­enced in a Canberra Times ar­ti­cle that was syn­di­cated to more than 60 news­pa­pers across Australia. It may also have cir­cu­lated through client brief­ings, in­ter­nal decks, and other pro­pri­etary me­dia that aren’t in the pub­lic do­main. Yet vibe ci­ta­tions don’t just de­ceive read­ers or cor­po­rate au­di­ences — they also have an­other, more in­sid­i­ous, im­pact.

Publishing a re­port on­line is es­sen­tially a form of data in­jec­tion into the pool of knowl­edge that is the in­ter­net. When the re­port in­cludes fake in­for­ma­tion (either vibed ci­ta­tions or false claims) it can poison the well” by mis­lead­ing fu­ture re­searchers, es­pe­cially if the re­port is pub­lished by a well-known con­sult­ing firm and hosted on a high-traf­fic web­site.

This risk has been ag­gra­vated by the emer­gence of AI deep re­search” tools which rely on dif­fer­ent sig­nals than hu­mans when choos­ing sources and are there­fore more vul­ner­a­ble to data poi­son­ing.

Conclusion

GPTZero is Chasing the Vibe (Citations)

Our re­search over the past few months proves that vibe cit­ing is a clear and pre­sent dan­ger to re­searchers, aca­d­e­mics, con­sul­tants, and (frankly) any­one who drinks from the dig­i­tal pool by search­ing the web. Our Hallucination Check tool is our an­swer to this threat: a way to iden­tify vibe ci­ta­tions and hal­lu­ci­na­tions with­out man­u­ally check­ing every ci­ta­tion. It is al­ready be­ing used to screen sub­mis­sions by elite aca­d­e­mic con­fer­ences like IJCAI, ICLR, and ICSE.

Now, more than ever, it’s crazy to ac­cept ci­ta­tions on faith — even those from a rep­utable source like Ernst & Young.

Try GPTZero’s Hallucination Check for your­self, or reach out to GPTZero’s team.

Written by Om Ogale

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

Visit pancik.com for more.