microsoft/typescript-go · error · Error

Error calling callback `${name}`: ${errMsg}

Error message

Error calling callback `${name}`: ${errMsg}

What it means

A registered host-side callback ran but threw an exception. The channel reports the failure to the child via MSG_CALL_ERROR and then re-throws in the parent, deliberately treating a failed callback as unrecoverable to match the native libsyncrpc addon's behavior — the pending request loop aborts.

Source

Thrown at _packages/native-preview/src/api/syncChannel.ts:366

            throw new Error(`no callback named \`${name}\` found`);
        }

        try {
            const result = cb(name, payload.toString("utf-8"));
            this.writeTuple(
                MSG_CALL_RESPONSE,
                Buffer.from(name, "utf-8"),
                Buffer.from(result, "utf-8"),
            );
        }
        catch (e: unknown) {
            const errMsg = String(e instanceof Error ? e.message : e).trim();
            this.writeTuple(
                MSG_CALL_ERROR,
                Buffer.from(name, "utf-8"),
                Buffer.from(errMsg, "utf-8"),
            );
            throw new Error(`Error calling callback \`${name}\`: ${errMsg}`);
        }
    }

    // ── MessagePack tuple write ─────────────────────────────────────

    /**
     * Write a complete [type, name, payload] tuple in as few writeSync
     * calls as possible.  For messages that fit in the pre-allocated
     * write buffer (64 KB), everything is assembled and sent in a single
     * syscall.  Larger messages use two syscalls: one for the header
     * portion and one for the payload data.
     */
    private writeTuple(type: number, name: Buffer, payload: Buffer | Uint8Array | string): void {
        const nameLen = name.length;
        const payloadIsString = typeof payload === "string";
        const payloadLen = payloadIsString ? Buffer.byteLength(payload, "utf-8") : payload.length;
        const nameHdrSize = binHeaderSize(nameLen);
        const payloadHdrSize = binHeaderSize(payloadLen);

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Fix the underlying exception reported in errMsg (the message names the callback and the cause)
  2. Make the callback defensive: return a well-defined error value instead of throwing when the child protocol allows it
  3. Validate inputs (path strings) before touching fs
  4. If the failure is transient (EBUSY on Windows), retry the fs operation inside the callback

Example fix

// before
channel.registerCallback('readFile', (name, path) => fs.readFileSync(path, 'utf8')); // ENOENT throws

// after
channel.registerCallback('readFile', (name, path) => {
  try {
    return fs.readFileSync(path, 'utf8');
  } catch (e) {
    return ''; // or encode an error the child understands
  }
});
Defensive patterns

Strategy: try-catch

Try / catch

try { channel.call('geterr', payload); } catch (e) { if (e.message.startsWith('Error calling callback')) { const cause = e.message.split(': ').slice(1).join(': '); /* fix the fs/permission cause, then restart the channel */ } throw e; }

Prevention

When it happens

Trigger: Any exception inside a registerCallback handler: fs.readFileSync on a missing/permission-denied path, JSON.parse of a malformed argument, or a bug in user callback code.

Common situations: Host callback doing filesystem access for the child when the file was deleted mid-session, EACCES on restricted paths, or a custom callback that assumes an argument format the child changed between versions.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/9423f60ae5d0ee02. Report an issue: GitHub.