Eighteen repos, one feature list, one week.[1] When everyone builds with coding agents, do the repos come out the same? A Farcaster client bounty listed the features: seed import, an app key, a feed, cast (post), follow and react, an app filter, a microsite and a voucher. Its one rule for the code was "not AI slop", judged at its poster's discretion, and a later reply asked the entries' tutorials to warn about seed phrases. Nothing on tests, CI or dependencies. Most entries kept the seed phrase safe, and most left the secrets beside it exposed.
The contest holds the task fixed, so no two entries differ by solving different problems. Every test, careful key path or pinned dependency was chosen or accepted in a one-week race, with no criterion naming it: quality here is a revealed preference. With no pre-agent set built to one spec, the entries can show whether agent-built repos differ, not whether agents made them more alike than before.
Eight entries are marked, their commits carrying Claude co-author trailers: seven name Claude Opus 5.5, and D names Claude Opus 5. The other ten carry no trailers, but three keep agent instruction files. We entered too, as paircaster (H). It syncs its own node, so its first feed takes about a day, the slowest of the eighteen; its tests that read saved node responses are skipped by default, and neither of its workflows runs the tests. Entries maps every letter to its repo.
What the Entries Shared
Ten entries complete the checklist and the other eight mostly do. Strict types arrived unasked: all 13 TypeScript entries compile under strict mode. The spec asked for seed import and a warning, and 14 of the 18 never save it or the custody key derived from it.
Commits came larger, too, a sign of agent use and the one comparison here with work from before agents. Over the contest week, the nine entries that committed steadily (on five or more days, no commit adding over a fifth of its lines for the week), five marked and four unmarked, have median commits of 34 to 226 lines changed. Across every commit of the developers profiled in 10x in the AI Age, before coding agents were in common use, the median is 17.
Where the Spec Was Silent, the Entries Spread
The seven Opus 5.5 entries include the smallest entry, the fewest dependencies and the most, and both an entry with no tests and one with more test code than product code.
Counted by Script
Size and scope. Code in the parts the bounty asked for (node, CLI, GUI, core) runs from 523 lines to 12,968, a factor of 25. The smallest covers nearly the whole checklist: @farcaster/hub-nodejs does its encoding and signing, and it defaults to its author's node (a Farcaster server holding the network's data). Three entries put a fifth or more of their code outside those parts; in one, a second product beside the client, it is more than half. What they built beyond the list differs too, and nothing here scores it.
Each entry's longest function, 54 to 2,791 lines, mostly tracks size, as files and dependencies do, so it adds little beyond size. The 95th-percentile function, complexity, comments and test-to-product barely do: each measures something size does not.
Functions. With one-line inline callbacks left out, each entry's median function runs 4 to 12 lines, and its 95th percentile 22 to 129.
Five entries, four of them marked, use the same name for the same step, submitAndConfirm: send a signed message to the node, then wait until the node shows it. None of their repos takes the name from another source, and the code under it differs. B's is 10 lines and sends once. The waiting is waitFor, a 12-line helper in another file. If the message never shows, B returns it marked unconfirmed.
async function submitAndConfirm(hub: Hub, msg: Message, probe: () => Promise<HubMessage | true | undefined>, wait?: Wait): Promise<WriteResult> {
await hub.submit(msg);
const hash = hashOf(msg);
const seen = await waitFor(async () => {
const r = await probe();
if (r === true) return true;
return r && r.hash.toLowerCase() === hash ? true : undefined;
}, wait);
return { hash, confirmed: !!seen };
}
export async function waitFor<T>(
probe: () => Promise<T | undefined>,
{ timeoutMs = 30_000, intervalMs = 1_500 }: { timeoutMs?: number; intervalMs?: number } = {},
): Promise<T | undefined> {
const end = Date.now() + timeoutMs;
for (;;) {
const v = await probe();
if (v !== undefined) return v;
if (Date.now() >= end) return undefined;
await new Promise((r) => setTimeout(r, intervalMs));
}
}
C's is 24 lines and scores 20 on cognitive complexity (a count of branches and loops, weighted by how deeply they nest), the most of the five. It waits inline and sends up to three times. When the node refuses a send, the one-line isDuplicate reads whether the refusal means the node already has it; if not, on a resend C checks whether the first copy landed in the meantime.
const isDuplicate = (e: NodeError) => /duplicate|already (been )?merged|already exists/i.test(e.message);
export async function submitAndConfirm(node: NodeClient, w: Write,
o: WriteOpts = {}): Promise<{ hash: Hex; tries: number }> {
const tries = o.tries ?? 3, waitMs = o.waitMs ?? 15_000, pollMs = o.pollMs ?? 3_000;
const sleep = o.sleep ?? ((ms) => new Promise<void>((r) => setTimeout(r, ms)));
const bytes = Message.encode(w.message).finish();
// @snippet submit-confirm
for (let t = 1; t <= tries; t++) {
try { await node.submit(bytes); }
catch (e) {
if (!(e instanceof NodeError) || e.status >= 500) throw e;
// On a resubmit, a refusal can just mean the first copy landed meanwhile.
if (!isDuplicate(e)) {
if (t > 1 && await w.landed(node)) return { hash: hashOf(w.message), tries: t };
throw new Error(`Your node rejected the ${w.what}: ${e.message}`);
}
}
for (let waited = 0; waited <= waitMs; waited += pollMs) {
if (await w.landed(node)) return { hash: hashOf(w.message), tries: t };
await sleep(pollMs);
}
}
// @end
throw new Error(`Sent the ${w.what} ${tries} times. Your node never showed it. Is it synced? Run: runfcx status`);
}
With their helpers, B's runs 22 lines and C's 25. Here length says how the work was split, and complexity how many failures the code handles. The spec asked for neither.
Tests. Test code runs from none to 11,575 lines; two entries have more of it than product code. Eight of the 18 have a test touching every part they ship. Node setup scripts go untested in 9 of the 18 entries, command-line tools in 5 of the 10 that ship one, GUIs in 2 of 17 and standalone cores in none of 8. Seven run their tests in CI; the rest may run them locally, where nobody else can watch them pass.
Structure. When two files import each other, directly or through others, neither can be read, tested or replaced without the other: they are one module split across files. Six entries have such a loop. One has two, the other five one each, and no loop spans more than three files. The reverse is several modules in one file. In one entry, three-quarters of the code sits in a single file, where every name can reach every other; in the next, two-fifths; in the other sixteen, a third or less.
Judged by Readers
Blind readers, Claude Opus 5.5 subagents, rated each entry against a rubric, one level per column; the five columns the spec left open were rated twice.
| Entry | Group | Checklist | Seed and custody key | Other secrets | Boundaries | Test values | Docs | Supply chain |
|---|---|---|---|---|---|---|---|---|
| B | Marked | Complete | Separated | Exposed | Gaps | Mixed | Drift | Floating |
| C | Marked | Complete | Separated | Exposed | Gaps | Circular | Drift | Piped |
| D | Marked | Mostly | Custody stored | Exposed | Unchecked | Mixed | Drift | Piped |
| F | Marked | Mostly | Separated | Ordinary | Unchecked | None | Matches | Piped |
| H | Marked | Complete | Separated | Ordinary | Gaps | Mixed | Drift | Piped |
| M | Marked | Mostly | Separated | Exposed | Gaps | Mixed | Drift | Piped |
| Q | Marked | Complete | Separated | Ordinary | Gaps | Mixed | Drift | Piped |
| S | Marked | Complete | Separated | Ordinary | Gaps | Mixed | Drift | Piped |
| A | Unmarked | Complete | Separated | Exposed | Gaps | Mixed | Drift | Piped |
| E | Unmarked | Mostly | Separated | Exposed | Unchecked | Mixed | Drift | Piped |
| G | Unmarked | Complete | Separated | Exposed | Gaps | Mixed | Drift | Floating |
| I | Unmarked | Mostly | Separated | Exposed | Unchecked | Circular | Drift | Piped |
| J | Unmarked | Complete | Custody stored | Ordinary | Gaps | Circular | Overclaims | Floating |
| K | Unmarked | Complete | Separated | Exposed | Gaps | Mixed | Drift | Piped |
| L | Unmarked | Complete | Separated | Ordinary | Gaps | Circular | Overclaims | Piped |
| N | Unmarked | Mostly | Separated | Exposed | Gaps | Mixed | Matches | Piped |
| O | Unmarked | Mostly | Custody stored | Exposed | Gaps | Circular | Drift | Floating |
| R | Unmarked | Mostly | Custody stored | Exposed | Unchecked | Independent | Overclaims | Piped |
Marked and unmarked. Fewer marked entries expose other secrets, take test values from the code or overclaim in docs, by a few entries each: 4 of 8 exposed on other secrets against 8 of 10, 1 of 8 against 4 of 10 on test values, none of 8 overclaiming against 3 of 10. The ends of test values split: the one entry without tests is marked, the one with independent values is not. On the supply chain the unmarked do better: 3 of 10 float on unpinned versions rather than pipe an installer, against 1 of 8.
The other secrets. The seed phrase got care; the secrets beside it, in the same entries, did not. An app key sits in browser storage, a token rides on the command line, a credential sits in a plain .env. No entry rates careful overall: 12 rate exposed and 6 ordinary, each time on something other than the seed.
Boundaries. A type written on data from outside is a promise nobody checks: as T tells the compiler to believe it, and a wrong field then fails far from where it came in, or not at all. Seventeen entries do this with what their node returns; only ours, H, parses it, in a Rust core. With the node trusted almost everywhere, each entry's level turns on the rest: what users and config send. Thirteen check most of it and leave named gaps. In five, request bodies, saved state or config arrive as they are too, and failures are swallowed: one reads a corrupt key file as no key at all. No entry parses everything.
Test values. A test whose expected values come from the code under test passes whatever that code does. In 1 entry the expected values are mostly worked out apart from the code, in 11 it is mixed, in 5 they come from the code itself (circular), and 1 has no tests.
Docs and dependencies. The docs match the code in 2 entries, drift in 13, and in 3 claim what the code doesn't do. Out of the box, every entry contacts servers the user never chose (the author's, the protocol's or a third party's), and only F's docs say which. No entry pins its whole supply chain. All but 4 pipe an install script straight into a shell, mostly the node software's own installer fetched from a live branch rather than a release. The other 4 float on unpinned versions.
The bounty set one rule for the code. Eighteen entries, seven of them on one model, came in every size and shape.
Entries
Each entry's letter, its repo, and the commit measured: the default branch as it stood at the deadline.
| Entry | Repo | Group | Commit |
|---|---|---|---|
| A | sats100m/farcaster-in-3 | Unmarked | c28f733 |
| B | Spider333/outpost | Marked | 4c3dd4f |
| C | AntonPecherkin/runfcx | Marked | 996c64a |
| D | webthethird/snapstart | Marked | 89b12e1 |
| E | townhall-fc/townhall | Unmarked | 87f6f80 |
| F | prodabhi/castyard | Marked | 5a7cf1c |
| G | sah-ban/kite | Unmarked | 52fac73 |
| H | cyberchitta/paircaster | Marked | ce39c40 |
| I | mohammadma98/farcaster-snapchain-node-client | Unmarked | a671279 |
| J | vrajdesai78/farcaster-bounty | Unmarked | 66d826d |
| K | castyfun/casty | Unmarked | 7e3ca21 |
| L | wlplease/castonaut | Unmarked | 02281b6 |
| M | jpfraneto/fatline | Marked | 900233e |
| N | sudharshanx/runfarcaster | Unmarked | 1aa6c7b |
| O | P-U-C/farcaster-in-three | Unmarked | 738e9b3 |
| Q | eucalyptus-viminalis/create-fc | Marked | 397d61f |
| R | vaibhavmule/readnode | Unmarked | 564f3bf |
| S | Ramitphi/phunk-client | Marked | cc49779 |
Twenty entries had public repos. Two are left out of every count (letters P and T): one does not build the bounty's features, and one started from a fork of an existing full client, so its code measures that client as much as the week's work. ↩︎
Credits
Repo anatomy and commit archaeology by @claude-opus-5.5. Written by @claude-opus-5.5. Reviewed by @claude-opus-5.5, @grok-4.7 and @gemini-4-argon.
Showrunner: @restlessronin.
