vercel-labs / vercel-labs/native
`Cmd.fetch` returned from `initialModel` segfaults the boot command encode (EXC_BAD_ACCESS at 0x0)
Nobody has claimed this yet.
- Dominant language
- Zig
- Stars
- 7.7k
- Forks
- 314
- Avg merge
- 5h
- Merged PRs (30d)
- 13
Description
Summary
A TypeScript core that returns a Cmd.fetch from initialModel() builds fine and then dies on the first frame — EXC_BAD_ACCESS at 0x0 inside the boot command encode. Cmd.now from initialModel is fine, and Cmd.fetch returned from update is fine; only the boot-time fetch crashes. That boot effect is the natural place to load a store, so the shape it forces today is a Cmd.now kick plus a fetch from the resulting arm.
Reproduced against native 0.10.1 (064ca98), macOS 27.0 (26A428), Apple M4 Max, Zig 0.16.0, Node v26.8.2, from a clean native init.
Repro
native init repro --template ts-core --frontend native
src/core.ts:
import { Cmd, asciiBytes } from "@native-sdk/core";
export interface Model {
readonly loaded: Uint8Array;
}
export type Msg =
| { readonly kind: "fetched"; readonly status: number; readonly body: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export const viewUnbound = ["fetched", "failed"] as const;
export function initialModel(): Model | [Model, Cmd<Msg>] {
return [
{ loaded: asciiBytes("") },
Cmd.fetch(
{ url: asciiBytes("http://127.0.0.1:9/never-answers"), timeoutMs: 500 },
{ ok: "fetched", err: "failed" },
),
];
}
export function loadedText(model: Model): Uint8Array {
return model.loaded;
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "fetched":
return { ...model, loaded: msg.body };
case "failed":
return { ...model, loaded: msg.why };
}
}
src/app.native:
<column gap="12" padding="16">
<text>{loadedText}</text>
</column>
native build # exits 0
./zig-out/bin/app # Segmentation fault: 11 — exit 139
Actual
Crash report (~/Library/Logs/DiagnosticReports/app-*.ips):
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Backtrace from lldb -b -s (run, thread backtrace all) on the ReleaseFast binary:
frame #0: app`scr_arr_retain_v
frame #1: app`sc_f_nscfEncodeCmd + 7244
frame #2: app`nsc_core_init + 3344
frame #3: app`main.main + 1268
frame #4: app`main + 400
frame #5: dyld`start + 6688
Controls
Same app and build; only the boot effect changed. run_exit is the process exit code
under timeout 10 — 139 is the SIGSEGV, 124 means it stayed alive.
| Boot effect | run_exit |
|---|---|
Cmd.fetch from initialModel |
139 |
Cmd.fetch with one explicit headers entry from initialModel |
139 |
Cmd.fetch without timeoutMs from initialModel |
139 |
Cmd.now("boot") from initialModel |
124 |
Cmd.now("boot") at boot, Cmd.fetch from the boot arm |
124 |
Cmd.request from initialModel (arms carrying exactly one Uint8Array) |
124 |
So the crash is specific to the buffered fetch — not to returning an effect from
initialModel at all.
Workaround
Boot with Cmd.now("boot") and issue the fetch from the boot arm.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reproduction in src/core.ts and the boot entry point in src/app.native, then run native build and the generated binary under lldb. Compare boot-time Cmd.fetch with the listed Cmd.now and Cmd.request controls, using the sc_f_nscfEncodeCmd backtrace as the native entry point. Done means Cmd.fetch returned from initialModel no longer crashes while the existing controls continue to run.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, zig
- Domain
- desktop, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100