ruvnet/RuView · error · Error
brain corpus exceeds 1 MiB
Error message
brain corpus exceeds 1 MiB
What it means
This is the firmware/privshield copy of the same daemon (byte-identical to wifi-veil/firmware/openwrt/veil_shieldd.c); the error is the event loop at lines 284-290 logging a negative return from nl_recvmsgs_default(). That libnl3 call receives and parses generic-netlink traffic on the nl80211 'mlme' group and runs the NL_CB_VALID callback veil_event_cb; any failure inside recv, parse, or callback dispatch yields a -NLE_* code. The loop whitelists only -NLE_AGAIN, so every other error (notably -NLE_INTR on signal shutdown, -NLE_SEQ_MISMATCH, -NLE_PARSE_ERR, -NLE_BAD_SOCK) aborts the daemon. Logging the raw integer and then returning exit status 0 hides both the cause and the failure from supervisors.
Source
Thrown at harness/homecore/src/brain.js:73
if (!/^[a-f0-9]{64}$/.test(record.source?.digest || '')) {
errors.push('source.digest must be a lowercase SHA-256 digest');
}
}
if (!Array.isArray(record.tags) || record.tags.some((tag) => typeof tag !== 'string')) {
errors.push('tags must be strings');
}
if ((record.content || '').length > 8192) errors.push('content exceeds 8192 characters');
if ((record.title || '').length > 200) errors.push('title exceeds 200 characters');
if (canonical && record.reviewed !== true) errors.push('canonical records must be reviewed');
const combined = `${record.title || ''}\n${record.content || ''}`;
if (SECRET.test(combined)) errors.push('record appears to contain a secret');
if (INJECTION.test(combined)) errors.push('record contains instruction-like prompt injection');
return errors;
}
export function loadBrain(path = CORPUS_PATH) {
const raw = readFileSync(path, 'utf8').replace(/\r\n/g, '\n');
if (Buffer.byteLength(raw) > 1_048_576) throw new Error('brain corpus exceeds 1 MiB');
const records = raw.split('\n').filter(Boolean).map((line, index) => {
if (Buffer.byteLength(line) > 16_384) {
throw new Error(`brain line ${index + 1}: exceeds 16 KiB`);
}
let record;
try {
record = JSON.parse(line);
} catch (error) {
throw new Error(`brain line ${index + 1}: ${error.message}`);
}
const errors = validateBrainRecord(record, { canonical: true });
if (errors.length) throw new Error(`brain line ${index + 1}: ${errors.join('; ')}`);
return Object.freeze(record);
});
if (records.length > 1000) throw new Error('brain corpus exceeds 1000 records');
const ids = new Set();
for (const record of records) {
if (ids.has(record.id)) throw new Error(`duplicate brain id: ${record.id}`);View on GitHub (pinned to 4685618388)
Solutions
- Decode the logged integer with the libnl3 NLE_* table (1=FAILURE, 2=INTR, 3=BAD_SOCK, 5=MSGSIZE, 14=SEQ_MISMATCH, 15=DUMP_INTR, 16=PARSE_ERR, 18=NOMEM) or switch the fprintf to nl_geterror(r) so the string name appears in the log.
- If it fires at shutdown (-NLE_INTR after SIGINT/SIGTERM), add -NLE_INTR to the tolerated codes or test g_ctx.running before breaking - it is not a real fault.
- Verify veil_event_cb returns only NL_OK/NL_SKIP and never a negative errno; guard every nla_find/nla_parse result before dereferencing.
- Confirm nl_socket_disable_seq_check(g_ctx.sock) (line 268) is still present in this copy - it is mandatory once the mlme multicast membership at line 86-89 is active.
- For ENOBUFS under event storms, call nl_socket_set_buffer_size(g_ctx.sock, 1<<20, 1<<20) before entering the loop, and confirm with strace.
- Keep all libnl use of g_ctx.sock on one thread; no other code path may nl_socket_free or recv on it.
- Change the tail to return non-zero when the loop exited while still running, so init scripts detect and restart the daemon; apply any fix to BOTH veil_shieldd.c copies (wifi-veil and privshield).
Example fix
// before (firmware/privshield/openwrt/veil_shieldd.c:284-293)
while (g_ctx.running) {
int r = nl_recvmsgs_default(g_ctx.sock);
if (r < 0 && r != -NLE_AGAIN) {
fprintf(stderr, "veil: nl_recvmsgs_default: %d\n", r);
break;
}
}
nl_socket_free(g_ctx.sock);
return 0;
// after
while (g_ctx.running) {
int r = nl_recvmsgs_default(g_ctx.sock);
if (r < 0 && r != -NLE_AGAIN && r != -NLE_INTR) {
fprintf(stderr, "veil: nl_recvmsgs_default: %d (%s)\n",
r, nl_geterror(r));
break;
}
}
nl_socket_free(g_ctx.sock);
return g_ctx.running ? 1 : 0; /* supervisors see the failure */ Defensive patterns
Strategy: try-catch
Validate before calling
/* Before the loop: cheap invariants that prevent the common fatal codes */ if (nl_socket_get_fd(g_ctx.sock) < 0) return 1; /* -NLE_BAD_SOCK */ if (g_ctx.family < 0) return 1; /* nl80211 unresolved */ nl_socket_set_buffer_size(g_ctx.sock, 1 << 20, 1 << 20); /* ENOBUFS on mlme */ /* line 268 must remain: nl_socket_disable_seq_check(g_ctx.sock); */
Type guard
/* C predicate: which receive errors are safe to survive */
static int nl_recv_transient(int r) {
return r == -NLE_AGAIN || r == -NLE_INTR || r == -NLE_DUMP_INTR;
} Try / catch
/* Wrap each receive in an explicit error branch; classify before acting */
int r = nl_recvmsgs_default(g_ctx.sock);
if (r < 0) {
if (nl_recv_transient(r)) continue; /* benign: next iteration */
fprintf(stderr, "veil: nl_recvmsgs_default: %s\n", nl_geterror(r));
exit_code = 1; /* do not mask as 0 */
break;
} Prevention
- Print nl_geterror(r), not just %d - the raw negative number is undebuggable on-target.
- Treat -NLE_INTR as expected on SIGINT/SIGTERM shutdown instead of a fatal event.
- Re-check this vendored copy after any change to the wifi-veil original: diff the two veil_shieldd.c files so one does not silently lose seq-check-disable or buffer sizing fixes.
- Keep veil_event_cb defensive: validate NLA lengths, return NL_SKIP for unrecognized mlme commands.
- Enlarge rcvbuf before subscribing to multicast groups; default buffers overflow during association storms.
- Return a non-zero exit status when the loop exits with g_ctx.running still set, so the OpenWrt init system restarts the daemon.
When it happens
Trigger: Concrete producers on this code path: (1) SIGINT/SIGTERM during the blocking receive - the handler sets g_ctx.running=0 but recvmsg returns EINTR, mapped by libnl to -NLE_INTR, which the loop treats as fatal and logs; (2) veil_event_cb returning negative after failing to parse an mlme notification - callback errors are propagated by nl_recvmsgs_default; (3) kernel ENOBUFS when mlme multicast bursts exceed the default rcvbuf, killing the loop mid-run; (4) -NLE_BAD_SOCK when the socket was closed/freed elsewhere or used from a second thread; (5) -NLE_SEQ_MISMATCH if this duplicated file drifts from the original and loses nl_socket_disable_seq_check() at line 268, which multicast events require; (6) an error ACK left on the socket by the veil_set_tx_antenna_mask() path when the driver rejects NL80211_CMD_SET_WIPHY.
Common situations: This duplicate lives in firmware/privshield/openwrt while the original sits in wifi-veil/firmware/openwrt - the classic vendored-copy failure is the two files diverging (one gets a fix, the other keeps the bug), so always patch both; running the scaffold (self-declared SYNTHETIC/L0, untested on silicon) against a real AP's mlme group; Ctrl-C or procd stop producing a scary error line on an otherwise clean shutdown; older OpenWrt libnl3 builds mapping unexpected errnos to -NLE_FAILURE, leaving the logged number ambiguous.
Related errors
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/2ae31f694eb21d63.
Report an issue: GitHub.