Skip to content

Commit 9da1fe4

Browse files
authored
fix: upgrade zod from v3 to v4 (#32)
## Summary - Bumps `zod` dependency from `^3.25.0` to `^4.3.6` - Fixes `src/config.ts` to use explicit fully-populated default objects for all nested schemas, required by Zod v4's changed `.default()` semantics (in v4, `.default({})` short-circuits and returns `{}` without applying inner field defaults, so defaults must be specified explicitly) - All 173 tests pass, typecheck clean
1 parent 876f639 commit 9da1fe4

4 files changed

Lines changed: 20 additions & 26 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,25 @@
2626
### Gotcha
2727

2828
<!-- lore:019c91d6-04af-7334-8374-e8bbf14cb43d -->
29-
* **Calibration used DB message count instead of transformed window count — caused layer 0 false passthrough**: Lore gradient calibration bugs that caused context overflow: (1) Used DB message count instead of transformed window count — after compression, delta saw ~1 new msg → layer 0 passthrough → overflow. Fix: getLastTransformedCount(). (2) actualInput omitted cache.write — cold-cache turns showed ~3 tokens instead of 150K → layer 0. Fix: include cache.write. (3) Trailing pure-text assistant messages cause Anthropic prefill errors, but messages with tool parts must NOT be dropped (SDK converts to tool\_result user-role). Drop predicate: \`hasToolParts\`. (4) Don't mutate message parts you don't own — removed stats PATCH that caused system-reminder persistence bug.
30-
31-
<!-- lore:019cb171-c0ef-7335-9afd-e7874b507b77 -->
32-
* **hostapd -t is not a config dry-run — it adds timestamps to debug output**: hostapd v2.10's \`-t\` flag means 'include timestamps in debug messages', NOT syntax check or dry-run. Running \`hostapd -t \<conf>\` fully initialises the interface and hangs as a running AP. There is no built-in config validation flag in hostapd. For validation, use grep-based checks for known-bad directives (e.g. checking for ieee80211r when it's not compiled in) rather than invoking hostapd itself.
33-
34-
<!-- lore:019c91c0-cdf3-71c9-be52-7f6441fb643e -->
35-
* **Lore plugin only protects projects where it's registered in opencode.json**: The lore gradient transform only runs for projects with lore registered in opencode.json (or globally in ~/.config/opencode/). Projects without it get zero context management — messages accumulate until overflow triggers a stuck compaction loop. This caused a 404K-token overflow in a getsentry/cli session with no opencode.json.
29+
* **Calibration used DB message count instead of transformed window count — caused layer 0 false passthrough**: Lore gradient/context management bugs and fixes: (1) Used DB message count instead of transformed window count — delta ≈ 1 after compression → layer 0 passthrough → overflow. Fix: getLastTransformedCount(). (2) actualInput omitted cache.write — cold-cache showed ~3 tokens → layer 0. Fix: include cache.write. (3) Trailing pure-text assistant messages cause Anthropic prefill errors. Drop loop must run at ALL layers including 0 — at layer 0 result.messages === output.messages (same ref), so pop() trims in place. Messages with tool parts must NOT be dropped (hasToolParts) — dropping causes infinite tool-call loops. (4) Lore only protects projects registered in opencode.json — unregistered projects get zero context management → stuck compaction loops creating orphaned message pairs. Recovery: delete all messages after last good assistant message (has tokens, no error).
3630

3731
<!-- lore:019cb171-c0ea-75cf-bf65-b081373f136b -->
3832
* **mt7921e 3dBm tx power on desktop — disable CLC firmware table**: mt7921e/mt7922 PCIe WiFi cards in desktop PCs (no ACPI SAR tables like WRDS/EWRD) get stuck at ~3 dBm tx power because the CLC (Country Location Code) firmware power lookup falls back to a conservative default when no SAR table exists. Fix: set \`options mt7921\_common disable\_clc=1\` in /etc/modprobe.d/mt7921.conf. This lets the regulatory domain ceiling apply (e.g. 23 dBm on 5GHz ch44 in GB). Also set explicit tx power via \`iw dev \<iface> set txpower fixed 2000\` in ExecStartPost since the module param only takes effect on next module load/reboot.
3933

4034
<!-- lore:019cb171-c0fa-74b0-a9a6-847901efa907 -->
4135
* **Pixel phones fail WPA group key rekey during doze — use 86400s interval**: Android Pixel devices in deep doze/sleep fail to respond to WPA group key handshake frames within hostapd's retry window. With wpa\_group\_rekey=3600, the phone gets deauthenticated every hour ('group key handshake failed (RSN) after 4 tries'). Other devices on the same AP complete the rekey fine. Fix: set wpa\_group\_rekey=86400 (24h) instead of 0 (disabled) for security balance. Also apply to Asus router: nvram set wpa\_gtk\_rekey=86400, wl0\_wpa\_gtk\_rekey=86400, wl1\_wpa\_gtk\_rekey=86400.
4236

43-
<!-- lore:019c91ad-4d47-7afc-90e0-239a9eda57a4 -->
44-
* **Stuck compaction loops leave orphaned user+assistant message pairs in DB**: When OpenCode compaction overflows, it creates paired user+assistant messages per retry (assistant has error.name:'ContextOverflowError', mode:'compaction'). These accumulate and worsen the session. Recovery: find last good assistant message (has tokens, no error), delete all messages after it from both \`message\` and \`part\` tables. Use json\_extract(data, '$.error.name') to identify compaction debris.
45-
4637
<!-- lore:019cb171-c0fe-78a8-a5f8-4ae8e2980a70 -->
4738
* **sudo changes $HOME to /root — hardcode user home in scripts run with sudo**: When running a script with \`sudo\`, \`$HOME\` resolves to \`/root\`, not the invoking user's home. SSH key paths like \`$HOME/.ssh/id\_ed25519\` break. Fix: use \`SUDO\_USER\` env var: \`USER\_HOME=$(eval echo ~${SUDO\_USER:-$USER})\` and reference \`$USER\_HOME/.ssh/id\_ed25519\`. This is a common trap in scripts that need both root privileges (systemctl, writing to /etc) and user-specific resources (SSH keys).
4839

4940
<!-- lore:019c8f4f-67ca-7212-a8c4-8a75b230ceea -->
50-
* **Test DB isolation via LORE\_DB\_PATH and Bun test preload**: Lore test suite uses an isolated temp DB via test/setup.ts preload (bunfig.toml). The preload sets LORE\_DB\_PATH to a mkdtempSync path before any test file imports src/db.ts, and the afterAll cleans up. src/db.ts checks LORE\_DB\_PATH first — if set, uses that exact path instead of ~/.local/share/opencode-lore/lore.db. agents-file.test.ts still needs beforeEach cleanup for intra-file isolation and TEST\_UUIDS cleanup in afterAll (shared explicit UUIDs with ltm.test.ts). Individual test files no longer need close() calls or cross-run cleanup beforeAll blocks — the preload handles DB lifecycle.
41+
* **Test DB isolation via LORE\_DB\_PATH and Bun test preload**: Lore test suite uses isolated temp DB via test/setup.ts preload (bunfig.toml). Preload sets LORE\_DB\_PATH to mkdtempSync path before any imports of src/db.ts; afterAll cleans up. src/db.ts checks LORE\_DB\_PATH first. agents-file.test.ts needs beforeEach cleanup for intra-file isolation and TEST\_UUIDS cleanup in afterAll (shared with ltm.test.ts). Individual test files don't need close() calls preload handles DB lifecycle.
5142

5243
<!-- lore:019cb171-c0f5-741f-96cc-e0862c846202 -->
53-
* **Ubuntu packaged hostapd lacks 802.11r (CONFIG\_IEEE80211R not compiled)**: Ubuntu 24.04's hostapd package (2:2.10-21ubuntu0.x) is compiled without CONFIG\_IEEE80211R. Using \`ieee80211r=1\`, \`mobility\_domain\`, \`ft\_over\_ds\`, \`r0kh\`, \`r1kh\`, or \`FT-PSK\` in wpa\_key\_mgmt causes 'unknown configuration item' errors and hostapd fails to start. 802.11k (rrm\_neighbor\_report, rrm\_beacon\_report) and 802.11v (bss\_transition) ARE compiled in and work. Verify with \`strings /usr/sbin/hostapd | grep ieee80211r\` — absence confirms no FT support. Building from source with CONFIG\_IEEE80211R=y is the only workaround.
44+
* **Ubuntu packaged hostapd lacks 802.11r (CONFIG\_IEEE80211R not compiled)**: Ubuntu 24.04 hostapd (2:2.10-21ubuntu0.x) lacks CONFIG\_IEEE80211R. Using \`ieee80211r=1\`, \`mobility\_domain\`, \`FT-PSK\` etc. causes 'unknown configuration item' and fails to start. 802.11k/v directives ARE compiled in. Verify: \`strings /usr/sbin/hostapd | grep ieee80211r\` — absence confirms no FT support. Build from source with CONFIG\_IEEE80211R=y. Note: hostapd has NO config dry-run flag — \`-t\` just adds timestamps to debug output and fully starts the AP. Use grep-based validation for known-bad directives instead.
45+
46+
<!-- lore:019cb286-7c85-7039-aecf-25781892c9da -->
47+
* **Zod v4 .default({}) no longer applies inner field defaults**: Zod v4 changed \`.default()\` to short-circuit: when input is \`undefined\`, it returns the default value directly without parsing it through inner schema defaults. So \`.object({ enabled: z.boolean().default(true) }).default({})\` returns \`{}\` (no \`enabled\` key), not \`{ enabled: true }\`. Fix: provide fully-populated default objects — \`.default({ enabled: true })\`. This affected all nested config sections in src/config.ts during the v3→v4 upgrade. The import \`import { z } from "zod"\` is unchanged — Zod 4's main entry point is the v4 API.
5448

5549
### Pattern
5650

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"dependencies": {
1919
"remark": "^15.0.1",
2020
"uuidv7": "^1.1.0",
21-
"zod": "^3.25.0"
21+
"zod": "^4.3.6"
2222
},
2323
"devDependencies": {
2424
"@opencode-ai/plugin": "^1.1.39",

pnpm-lock.yaml

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ export const LoreConfig = z.object({
1515
/** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.10 (10%). */
1616
ltm: z.number().min(0.02).max(0.3).default(0.10),
1717
})
18-
.default({}),
18+
.default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.10 }),
1919
distillation: z
2020
.object({
2121
minMessages: z.number().min(3).default(8),
2222
maxSegment: z.number().min(5).default(50),
2323
metaThreshold: z.number().min(3).default(10),
2424
})
25-
.default({}),
25+
.default({ minMessages: 8, maxSegment: 50, metaThreshold: 10 }),
2626
knowledge: z
2727
.object({
2828
/** Set to false to disable long-term knowledge storage and system-prompt injection.
@@ -32,7 +32,7 @@ export const LoreConfig = z.object({
3232
* system prompt. Default: true. */
3333
enabled: z.boolean().default(true),
3434
})
35-
.default({}),
35+
.default({ enabled: true }),
3636
curator: z
3737
.object({
3838
enabled: z.boolean().default(true),
@@ -41,15 +41,15 @@ export const LoreConfig = z.object({
4141
/** Max knowledge entries per project before consolidation triggers. Default: 25. */
4242
maxEntries: z.number().min(10).default(25),
4343
})
44-
.default({}),
44+
.default({ enabled: true, onIdle: true, afterTurns: 10, maxEntries: 25 }),
4545
pruning: z
4646
.object({
4747
/** Days to keep distilled temporal messages before pruning. Default: 120. */
4848
retention: z.number().min(1).default(120),
4949
/** Max total temporal_messages storage in MB before emergency pruning. Default: 1024 (1 GB). */
5050
maxStorage: z.number().min(50).default(1024),
5151
})
52-
.default({}),
52+
.default({ retention: 120, maxStorage: 1024 }),
5353
crossProject: z.boolean().default(true),
5454
agentsFile: z
5555
.object({
@@ -58,7 +58,7 @@ export const LoreConfig = z.object({
5858
/** Path to the agents file, relative to the project root. */
5959
path: z.string().default("AGENTS.md"),
6060
})
61-
.default({}),
61+
.default({ enabled: true, path: "AGENTS.md" }),
6262
});
6363

6464
export type LoreConfig = z.infer<typeof LoreConfig>;

0 commit comments

Comments
 (0)