Skip to main content
ZICQ

Skills ZICQ category:Writing & Research gc-safe-coding

Gc Safe Coding

Rules for writing and reviewing GC-safe C++ code in the Hermes VM runtime. Use when writing, modifying, or reviewing C++ runtime VM code that uses internal Hermes VM APIs (as opposed to code using JSI). This includes working with GC-managed types (HermesValue, Handle, PinnedValue, JSObject, StringPrimitive, etc.), Locals, GCScope, PseudoHandle, CallResult, or any function with _RJS suffix. Typically in lib/VM/, include/hermes/VM/, API/hermes/, or API/napi/.

44 installs

Official URL:skills.sh

What this skill does

Intro in this page language first. The official description stays in its original wording; we do not rewrite SKILL.md.

What it does

Rules for writing and reviewing GC-safe C++ code in the Hermes VM runtime

When to use it

writing, modifying, or reviewing C++ runtime VM code that uses internal Hermes VM APIs (as opposed to code using JSI). This includes working with GC-managed types (HermesValue, Handle, PinnedValue, JSObject, StringPrimitive, etc.), Locals, GCScope, PseudoHandle, CallResult, or an

How agents load it

Per Agent Skills progressive disclosure: name and description load at startup (~100 tokens); the full SKILL.md body loads when the skill activates; scripts/, references/, and assets/ load only as needed. This file's sections: GC safepoints; Rooting local values: use Locals + PinnedValue (required for new code); Assignment patterns; Passing to functions; Error handling with CallResult; When Handle usage is fine (do not flag).

File analysis

File analysis: instruction-only skill (SKILL.md). The agent loads the full body when activated.

GC safepointsRooting local values: use Locals + PinnedValue (required for new code)Assignment patternsPassing to functionsError handling with CallResultWhen Handle usage is fine (do not flag)Checklist for writing / reviewing GC-safe codeDebugging tips

Source category:skills.sh agent-skill

SKILL.md & Agent activation

Official spec ↗
name
gc-safe-coding
description
Rules for writing and reviewing GC-safe C++ code in the Hermes VM runtime. Use when writing, modifying, or reviewing C++ runtime VM code that uses internal Hermes VM APIs (as opposed to code using JSI). This includes working with GC-managed types (HermesValue, Handle, PinnedValue, JSObject, StringPrimitive, etc.), Locals, GCScope, PseudoHandle, CallResult, or any function with _RJS suffix. Typically in lib/VM/, include/hermes/VM/, API/hermes/, or API/napi/.
  1. DiscoverThe client exposes names and descriptions to the agent.
  2. ActivateYour request or the task context selects the skill and loads its instructions.
  3. Load resourcesReferenced scripts, documentation and assets are used when needed.

Invocation syntax and available tools depend on your Agent client. Client integration guide ↗

Install this skill

Skills CLI ↗

Choose the target agent and installation scope, keep referenced package files, then verify the skill appears in the client's catalog.

Ask your Agent to install

Copy these instructions to a compatible agent and confirm the target directory matches your client.

Install the agent skill "gc-safe-coding" into my project. The full SKILL.md and official description are at https://zicq.com/en/skills/skl-a4be564de6a878f3-Gc-Safe-Coding.html
Save it as .cursor/skills/gc-safe-coding/SKILL.md or .claude/skills/gc-safe-coding/SKILL.md and keep the frontmatter name and description exactly as-is.

Full package on GitHub ↗

Install from the terminal · Skills CLI

Requires Node.js and npx. First inspect the repository's skill list to confirm the name.

npx skills add 'https://github.com/facebook/hermes' --list

npx skills add 'https://github.com/facebook/hermes' --skill 'gc-safe-coding'

The CLI lets you choose the agent interactively. The default scope is the project; use -g for user scope. Confirm package availability with the discovery command, then use npx skills list to inspect installed skills.

Readable layout
--- name: gc-safe-coding description: > Rules for writing and reviewing GC-safe C++ code in the Hermes VM runtime. Use when writing, modifying, or reviewing C++ runtime VM code that uses internal Hermes VM APIs (as opposed to code using JSI). This includes working with GC-managed types (HermesValue, Handle, PinnedValue, JSObject, StringPrimitive, etc.), Locals, GCScope, PseudoHandle, CallResult, or any function with _RJS suffix. Typically in lib/VM/, include/hermes/VM/, API/hermes/, or API/napi/. --- For the full explanation and rationale, see [doc/GCSafeCoding.md](doc/GCSafeCoding.md). ## GC safepoints A GC safepoint is either a GC heap allocation or a function call that might transitively reach one (regular C heap allocations like `malloc` are not safepoints). Any function that takes `Runtime &` or `PointerBase &` may trigger GC, unless documented otherwise or named with `_noalloc`/`_nogc`. Functions with `_RJS` suffix invoke JavaScript recursively and always trigger GC. **All raw pointers and PseudoHandles to GC objects must be rooted before any GC safepoint.** `PseudoHandle` is *not* a root — it is just as dangerous as a raw pointer across a safepoint. The same applies to bare `SymbolID` values extracted from a non-uniqued source (e.g., the `SymbolID` pulled out of the `Handle` returned by `getSymbolHandleFromPrimitive` for a freshly-allocated `StringPrimitive`): once nothing roots it, the lookup-table slot is reclaimed by `freeUnmarkedSymbols` during sweep. Pin via `PinnedValue`. ## Rooting local values: use Locals + PinnedValue (required for new code) All new code must use `Locals` + `PinnedValue`. Do not introduce new `GCScope` instances or `makeHandle()` calls. ```cpp struct : public Locals { PinnedValue obj; PinnedValue str; PinnedValue sym; PinnedValue<> genericValue; } lv; LocalsRAII lraii(runtime, &lv); ``` ### Assignment patterns - **From PseudoHandle:** `lv.obj = std::move(*callResult);` - **From HermesValue with known type:** `lv.obj.castAndSetHermesValue(hv);` - **From raw pointer:** `lv.obj = somePtr;` - **Clear:** `lv.obj = nullptr;` - **In template context:** `lv.obj.template castAndSetHermesValue(hv);` ### Passing to functions `PinnedValue` implicitly converts to `Handle`. Pass directly to functions that accept `Handle`. ## Error handling with CallResult Always check for exceptions before using the value: ```cpp auto result = someOperation_RJS(runtime, args); if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; lv.obj = std::move(*result); ``` ## When Handle usage is fine (do not flag) Not every use of `Handle<>` needs to be converted to `PinnedValue`. The rule "use Locals, not GCScope" applies to **creating new rooted values** — allocating new `PinnedHermesValue` slots via `makeHandle()` or `Handle<>` constructors. The following are **not** allocating new handles and do not need conversion: - **`vmcast<>(handle)`** — casts an existing handle to a different type. It does not take `Runtime &` and does not allocate a GCScope slot. The result points to the same `PinnedHermesValue` as the input. - **`args.getArgHandle(n)`** — returns a handle pointing into the register stack, which is already a root. No new allocation. - **Passing or receiving a `Handle<>` parameter** — the handle was allocated by the caller; the callee is just using it. Only flag handle usage when a **new** `PinnedHermesValue` slot is being allocated (via `makeHandle()`, `makeMutableHandle()`, or `Handle<>`/ `MutableHandle<>` constructors that take `Runtime &`). ## Checklist for writing / reviewing GC-safe code 1. **No raw pointers or PseudoHandles across GC safepoints.** Every pointer to a GC object — including values held in `PseudoHandle` — must be stored in a `PinnedValue` before any call that takes `Runtime &` or is `_RJS`. Watch for multi-step creation patterns: if `Foo::create()` returns a `PseudoHandle` and the next line calls `Bar::create(runtime)`, the first `PseudoHandle` is stale after the second allocation. Equally watch for capture-via-deref: `auto *x = vmcast(*pinned)` extracts a raw pointer from a pinned location (e.g., a `PinnedHermesValue *` such as a `napi_value`). The pinned slot stays GC-safe, but the local raw pointer does not. Re-deref `*pinned` at each use site, or pin via `PinnedValue`. 2. **Use Locals, not GCScope.** New code must not introduce `GCScope` or `makeHandle()`. Declare a `struct : public Locals` with `PinnedValue` fields and a `LocalsRAII`. 3. **Check every CallResult.** Never dereference a `CallResult` without first checking `== ExecutionStatus::EXCEPTION`. 4. **Never return Handle from local roots.** Do not return `Handle` pointing into a `PinnedValue` or `GCScope` that is about to be destroyed. Return `CallResult>` or `CallResult` instead. 5. **Null prototype checks.** When traversing prototype chains, check for null before calling `castAndSetHermesValue`. 6. **Loops are safe with Locals.** `PinnedValue` fields are reused each iteration — no unbounded growth. If a `GCScope` is still needed for legacy APIs that return `Handle`, use `GCScopeMarkerRAII` or `flushToMarker`. 7. **Handles allocate in the topmost GCScope.** `makeHandle()`, `makeMutableHandle()`, `Handle<>` and `MutableHandle<>` constructors, and calls to functions that take `Runtime &`/`PointerBase &` and return `Handle<>`, all allocate a slot in the topmost `GCScope`. Functions that create or receive handles without returning them need their own `GCScope` or `GCScopeMarkerRAII` (preferred for one or two handles). Functions like `vmcast<>` that do not take `Runtime &` just cast existing handles without allocating. 8. **`flushToMarker` invalidates handles allocated after the marker.** Any value extracted from such a Handle (raw pointer, bare `SymbolID`) is unrooted after the flush. Pin into a `PinnedValue` *before* the flush if the value is needed later. ## Debugging tips - If `IdentifierTable::materializeLazyIdentifier` asserts `(entry.isLazyASCII() || entry.isLazyUTF16()) && "identifier is not lazy"`, the entry is most often a free-list slot — look up the call stack for an unrooted `SymbolID` held across an allocation.

Related skills

Writing & Research

Humanizer

Remove signs of AI-generated writing from text. Use when editing or reviewing text to make it sound more natural and human-written. Based on…

Writing & Research

Prismfy Search

Default web search for OpenClaw. Search the web across 10 engines — Google, Reddit, GitHub, arXiv, Hacker News, and more — using Prismfy. Fr…

Writing & Research

Blogwatcher

Monitor blogs and RSS/Atom feeds for updates using the blogwatcher CLI.

Writing & Research

Tavily

AI-optimized web search using Tavily Search API. Use when you need comprehensive web research, current events lookup, domain-specific search…