Agent skill

Debug Php Wasm Main Module

by WordPress in WordPress/wordpress-playground

Debug PHP.wasm main module crashes including Asyncify errors (unreachable, memory access out of bounds), JSPI errors (SuspendError, trying to suspend JS frames), WASM memory growth bugs, and runtime…

GPL-2.0Auto-check passedDevelopment

Install Debug Php Wasm Main Module

skills CLI
$ npx skills add WordPress/wordpress-playground --skill debug-php-wasm-main-module -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install WordPress/wordpress-playground debug-php-wasm-main-module --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/WordPress/wordpress-playground.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/debug-php-wasm-main-module .claude/skills/debug-php-wasm-main-module && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
debug-php-wasm-main-module
GitHub stars
2k
Token cost
~3.1k tokens
SKILL.md length
1,367 words
Files
1
Skills in repo
5
Repo updated
First seen
Licence
GPL-2.0

At a glance

Debug PHP.wasm main module crashes including Asyncify errors (unreachable, memory access out of bounds), JSPI errors (SuspendError, trying to suspend JS frames), WASM memory growth bugs, and runtime…

  • Works in 6 steps: Run the test with… → Identify the async trigger in the stack… → Work backwards through the call chain… → …
  • Investigating RuntimeError
  • SKILL.md covers Error Message Interpretation, Asyncify Crash Debugging…, JSPI Debugging and PHP Startup Lifecycle in WASM, plus 5 more sections
  • Calls npx and npm

What it does

Debug Php Wasm Main Module is an agent skill from WordPress/wordpress-playground. Debug PHP.wasm main module crashes including Asyncify errors (unreachable, memory access out of bounds), JSPI errors (SuspendError, trying to suspend JS frames), WASM memory growth bugs, and runtime traps. Use when investigating RuntimeError, null function or signature mismatch, or other WASM-related crashes in the main PHP binary.

Its SKILL.md is about 3.1k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Development, covering Debugging. It works with WebAssembly, PHP and WordPress. The repository describes itself as: Run WordPress in the browser via WebAssembly PHP. The licence is GPL-2.0.

When your agent uses it

  • Investigating RuntimeError
  • Signature mismatch
  • Other WASM-related crashes in the main PHP binary

Example prompts

  • “/debug-php-wasm-main-module”

Requirements

  • Node.js

Workflow steps

6 steps, taken from the first numbered list in SKILL.md.

  1. Run the test with --stack-trace-limit=200 (default 10 is too
  2. Identify the async trigger in the stack trace (e.g.
  3. Work backwards through the call chain from the trigger
  4. Start with the opcode handler — it's usually the critical missing
  5. Add one function at a time to ASYNCIFY_ONLY. Recompile and
  6. Repeat until the crash is fixed or a new crash surfaces (often

What it can do on your machine

Read from SKILL.md and the folder at commit d7a515d. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npx
    • npm

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use npx and npm, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Debug Php Wasm Main Module loads about 3.1k tokens when it runs. Until then it costs about 90 tokens; SKILL.md has 1,367 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~90
When it runs · the whole SKILL.md, loaded when a task matches
~3.1k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from WordPress/wordpress-playground at commit d7a515d, republished under its GPL-2.0 licence (© WordPress). 1,367 words, ~3,090 tokens.

Download SKILL.mdSave it as .claude/skills/debug-php-wasm-main-module/SKILL.md (or your agent's skills folder).
name
debug-php-wasm-main-module
description
Debug PHP.wasm main module crashes including Asyncify errors (unreachable, memory access out of bounds), JSPI errors (SuspendError, trying to suspend JS frames), WASM memory growth bugs, and runtime traps. Use when investigating RuntimeError, null function or signature mismatch, or other WASM-related crashes in the main PHP binary.

Debugging PHP.wasm Main Module

Patterns for diagnosing and fixing crashes in the main PHP.wasm binary — Asyncify unwind/rewind failures, JSPI suspension errors, memory growth bugs, and runtime WASM traps.

Error Message Interpretation

Asyncify errors
Error messageLikely cause
RuntimeError: unreachableA function on the call stack is missing from ASYNCIFY_ONLY
memory access out of boundsAn opcode handler is missing — Asyncify corrupts the stack during rewind
null function or signature mismatchMissing ASYNCIFY_ONLY function elsewhere on the stack — corrupted Asyncify state causes this to manifest in a different function than the one actually missing
table index is out of boundsMissing opcode handler (variant of the above)

Secondary errors (undefined variable, corrupted state) after any of these are red herrings caused by the corrupted Asyncify rewind.

JSPI errors
Error messageLikely cause
SuspendError: trying to suspend JS framesA JS frame sits between two WASM frames in the call stack. JSPI can only suspend pure WASM stacks. Common causes: (1) JS trampoline in the call chain; (2) C++ side module weak symbol env imports resolved through JS closure stubs
SuspendError: trying to suspend without WebAssembly.promisingThe WASM function calling a suspending JS import is not in JSPI_EXPORTS
null function or function signature mismatch (after side module load)Side module loading corrupted the function table — check Emscripten version match between main and side module
Same root cause, different errors across PHP versions

A single missing ASYNCIFY_ONLY function produces different WASM error types depending on the PHP version:

  • PHP 5.6: table index is out of bounds
  • PHP 7.0: null function or function signature mismatch
  • PHP 7.2/8.2: memory access out of bounds
  • PHP 7.4/8.0/8.1: unreachable or memory access out of bounds

Each PHP version compiles to different WASM code for the same opcode handler. Don't assume different error messages mean different bugs — always check the function at the top of the WASM stack trace.

Asyncify Crash Debugging Strategy

Step-by-step process
  1. Run the test with --stack-trace-limit=200 (default 10 is too shallow for Asyncify crashes)
  2. Identify the async trigger in the stack trace (e.g. _emscripten_sleep, _wasm_recv)
  3. Work backwards through the call chain from the trigger
  4. Start with the opcode handler — it's usually the critical missing function. Adding deeper utility functions first won't help if the opcode handler isn't instrumented.
  5. Add one function at a time to ASYNCIFY_ONLY. Recompile and re-test after each addition. This reveals which function was actually needed and whether deeper functions are now exposed.
  6. Repeat until the crash is fixed or a new crash surfaces (often deeper in the stack — fixing one crash reveals the next)
What needs ASYNCIFY_ONLY

Every function on the call stack at the moment of the async call needs Asyncify instrumentation. This includes:

  • Opcode handlers (ZEND_*_SPEC_*_HANDLER) — always check these first
  • Bridge functions between the opcode and the async call (e.g. zend_user_it_get_new_iterator for iterator creation)
  • Cleanup functions that run in the same scope before the suspension point (var_destroy, _efree_large, php_var_unserialize_destroy) — these are NOT just post-crash artifacts
What does NOT need ASYNCIFY_ONLY
  • Functions that run after the async operation returns
  • Error formatting functions (xbuf_format_converter, php_printf_to_smart_str) that appear because the failed rewind triggered zend_error — these are red herrings
Common function categories to instrument

Iterator operations (spread, foreach, array unpack):

  • ZEND_ADD_ARRAY_UNPACK_SPEC_HANDLER, ZEND_FE_FETCH_R_SPEC_VAR_HANDLER
  • zend_user_it_get_new_iterator, zend_user_it_move_forward

Stream operations:

  • _php_stream_make_seekable, _php_stream_copy_to_stream_ex
  • _php_stream_flush, _php_stream_cast, zif_stream_select

Object operations:

  • zend_std_write_property, zend_std_cast_object_tostring
  • zend_objects_clone_obj, zend_objects_clone_members

Error/exception handling:

  • zend_error, zend_error_zstr, zend_throw_exception
  • zend_undefined_index

Serialization:

  • zif_serialize, zif_unserialize, php_var_unserialize_destroy

JSPI Debugging

Vitest must have --experimental-wasm-jspi

Node.js requires this flag for JSPI. Without it, wasm-feature-detect's jspi() returns false and getPHPLoaderModule silently loads the asyncify build. All JSPI bugs become invisible.

Add to vite.config.ts:

ts
poolOptions: {
    forks: {
        execArgv: ['--expose-gc', '--experimental-wasm-jspi'],
    },
},

Always verify which build is loaded by adding a console.log to the JS glue file.

Gate JSPI vs Asyncify paths

Use wasm-feature-detect's jspi() function to branch between JSPI (dynamic extensions) and Asyncify (static extensions) code paths — both in runtime loading and in test files.

Synchronous JS imports must NOT be marked async

When a WASM JS import has isAsync = true, JSPI wraps it with WebAssembly.Suspending. Even if the implementation never suspends (returns a value, not a Promise), the wrapper corrupts the WASM call stack — V8's native stack bookkeeping desynchronizes __stack_pointer, causing heap corruption that manifests later as zend_mm_panic in _efree.

Symptoms: crash only during PHP startup (php_module_startup), heap corruption in unrelated code (zend_hash_destroy, zend_file_handle_dtor).

Debugging strategy: neuter the JS import (return 0 immediately). If the crash persists, the problem is the JSPI wrapping, not the import's implementation. Check functionName.isAsync in the compiled JS glue. Fix by setting functionName__async: false in the Emscripten JS library.

Check JSPI wrapping in compiled glue

Search the compiled JS glue for:

  • instrumentWasmImports → importPattern regex (imports wrapped with WebAssembly.Suspending)
  • instrumentWasmExports → exportPattern regex (exports wrapped with WebAssembly.promising)

A function in the import pattern that shouldn't suspend causes heap corruption. A function that needs to suspend but isn't in the pattern returns immediately instead of waiting.

PHP Startup Lifecycle in WASM

loadNodeRuntime() / loadWebRuntime()  →  WASM module loads, FS ready
new PHP(runtime)                      →  initializeRuntime(), writes default php.ini
php.run()                             →  php_wasm_init() → php_module_startup()
                                         → parses ini, initializes modules

Crashes only during step 3 (startup) but not at runtime point to WASM-JS boundary issues (JSPI wrapping, calling conventions) rather than PHP logic bugs.

Show full SKILL.md (537 more words)Show less

WASM Memory Growth Bugs

memory.grow() detaches the old ArrayBuffer. Emscripten's updateMemoryViews() replaces module-scoped HEAP variables, but any JS code that captured a typed array reference (object literal, destructuring, closure) now points to a detached buffer.

Symptoms: SQLITE_IOERR from file locking, reads return zero, writes are silent no-ops — all appearing after the WASM module has been running for a while (memory grew).

Fix pattern

Never expose raw typed arrays across module boundaries. Use accessor objects:

js
memory: {
    HEAP16: {
        get(offset) { return HEAP16[offset]; },
        set(offset, value) { HEAP16[offset] = value; },
    }
}

This makes stale capture structurally impossible. Property getters (get HEAP16() { return HEAP16; }) still expose the typed array, which callers can capture — accessor objects are safer.

Asyncify allocation bug

Emscripten's handleSleep() calls _malloc() on every async unwind. If that triggers memory.grow(), Asyncify state corrupts. Fix: cache allocateData() result, reuse across sleeps. Apply via Dockerfile replace.sh (Asyncify-only, not JSPI).

Reproducing without recompilation

INITIAL_MEMORY is baked into the WASM binary (typically 256MB). Force growth from PHP:

php
str_repeat('x', 300 * 1024 * 1024);

Or set a low INITIAL_MEMORY (64MB) during compilation to force earlier growth.

Tracing the WASM-JS Boundary

When a PHP.wasm feature silently fails (no crash, no error, just doesn't work):

Instrument JS imports in the compiled glue

Add console.log to JS functions in the compiled glue file (php_8_4.js). Search for function ___ (triple underscore) to find Emscripten's syscall wrappers. Log arguments to see what WASM is passing.

If a C function is called in the source but the corresponding JS wrapper never fires, the symbol resolution is wrong.

Inspect WASM module imports and exports
js
const mod = new WebAssembly.Module(fs.readFileSync('path/to/module.wasm'));
console.log(WebAssembly.Module.imports(mod).map(i => i.name));
console.log(WebAssembly.Module.exports(mod).map(e => e.name));

A function in the C source but NOT in the module's imports list was inlined, stubbed, or resolved statically — it won't call through to JS.

Add printf to C source

When the JS glue is not enough, add fprintf(stderr, ...) statements to the PHP C source code and rebuild. This traces the actual execution path through the WASM binary. Use this when:

  • The error message is ambiguous
  • You need to know what values are passed at SAPI/extension boundaries
  • Execution diverges from expectation with no visible error

Test Infrastructure Gotchas

  • assertNoCrash silently swallows errors when FIX_DOCKERFILE is not set. Always add a re-throw after the catch block.
  • Floating promises + php.exit() = unhandled rejections. Always return or await calls to assertNoCrash().
  • Vitest misattributes unhandled rejections to the wrong test (test N rejection surfaces during test N+1).
  • PHP 8.4 deprecation notices break expect(result.text).toBe(''). Fix: add proper return types or wrap with ob_start()/ob_end_clean().
  • WASM fires secondary crashes from sapi_send_headers as uncaught exceptions (not promise rejections). Tests must handle both unhandledRejection and uncaughtException.
  • HTTPS tests need the CA cert in WASM FS. Write the cert file and set openssl.cafile via setPhpIniEntries.

Testing Commands

bash
# Run tests for specific PHP version + mode
PHP=8.0 npm run test-group-3-asyncify

# Filter tests by name
npx nx test php-wasm-node --testFile=php.spec.ts -- --test-name-pattern='Magic Methods'

# Increase stack trace depth (critical for Asyncify crashes)
NODE_OPTIONS='--stack-trace-limit=200' npx nx test php-wasm-node

# Verbose output
npx nx test php-wasm-node -- --reporter=verbose

Diagnostic Cheat Sheet

SituationAction
unreachable / memory access out of boundsAsyncify crash — find missing ASYNCIFY_ONLY function
SuspendError: trying to suspend JS framesJS frame in WASM call stack — eliminate JS trampoline
SuspendError: ... without WebAssembly.promisingAdd function to JSPI_EXPORTS
zend_mm_panic in _efreeCheck for wrongly-async JS imports (JSPI wrapping issue)
Startup hang (all tests time out)JSPI syscall wrapper gained JS frame — remove from JSPI lists
SQLITE_IOERR after running a whileStale HEAP reference after memory.grow()
Different errors across PHP versionsSame root cause — check function at top of WASM stack
Silent failure (no crash, no error)Trace WASM-JS boundary — instrument glue file
Test passes but shouldn'tCheck for assertNoCrash swallowing errors

© WordPress, GPL-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/debug-php-wasm-main-module of WordPress/wordpress-playground.

Open the folder on GitHubat commit d7a515d

Compare with similar skills

Debug Php Wasm Main Module next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Debug Php Wasm Main Module compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Debug Php Wasm Main Module this skillWordPress/wordpress-playground2k—~3.1kAutomated safety check: PassGPL-2.0
Wp Interactivity APIAutomattic/agent-skills2112 repos~1.5kAutomated safety check: PassNone
Wp PlaygroundAutomattic/agent-skills2111 repos~1.2kAutomated safety check: PassNone
WooCommerce Code Reviewwoocommerce/woocommerce11k3 repos~1.1kAutomated safety check: PassCustom licence
WooCommerce Dev Cyclewoocommerce/woocommerce11k3 repos~431Automated safety check: PassCustom licence
Staticphp Documentation Synccrazywhalecc/static-php-cli1.9k—~2.2kAutomated safety check: PassMIT

Similar skills

  • Wp Interactivity API

    Automattic/agent-skills

    A skill your agent uses when building or debugging WordPress Interactivity API features (data-wp- directives, @wordpress/interactivity store/state/actions, block viewScriptModule integration…

    211 GitHub starsUsed in 2 repos~1.5k tokens
    DevelopmentAuto-check passed
  • Wp Playground

    Automattic/agent-skills

    A skill your agent uses for WordPress Playground workflows: fast disposable WP instances in the browser or locally via @wp-playground/cli (server, run-blueprint, build-snapshot), auto-mounting…

    211 GitHub starsUsed in 1 repo~1.2k tokens
    DevelopmentAuto-check passed
  • WooCommerce Code Review

    woocommerce/woocommerce

    Reviews WooCommerce code changes against the project's standards, flagging backend PHP architecture, naming, documentation, data integrity and testing violations.

    11k GitHub starsUsed in 3 repos~1.1k tokens
    DevelopmentAuto-check passed
  • WooCommerce Dev Cycle

    woocommerce/woocommerce

    Workflow for WooCommerce development: run PHP and JavaScript tests, lint and fix code style on the current branch, and follow guides for i18n and markdown.

    11k GitHub starsUsed in 3 repos~431 tokens
    DevelopmentAuto-check passed
  • Staticphp Documentation Sync

    crazywhalecc/static-php-cli

    Synchronize bilingual documentation when StaticPHP v3 user-facing or developer-facing documentation must change.

    1.9k GitHub stars~2.2k tokensUpdated yesterday
    DevelopmentAuto-check passed
  • Blueprint

    bonny/WordPress-Simple-History

    A skill your agent uses when the deliverable is WordPress Playground Blueprint JSON or a Blueprint bundle, including creating, editing, reviewing, validating schema keys, choosing steps/resources…

    317 GitHub starsUsed in 1 repo~4k tokens
    DevelopmentAuto-check passed

More from WordPress/wordpress-playground

  • Doc Screenshots

    WordPress/wordpress-playground

    Annotate UI screenshots with documentation callouts in Fellyph's established visual style — uniform-width orange arrows with white halos, double-stroke target outlines, numbered callout cards, dim…

    2k GitHub stars~1.8k tokensUpdated yesterday
    Auto-check passed
  • Compile Php Wasm

    WordPress/wordpress-playground

    Compile PHP.wasm main modules and side modules (dynamic extensions) for Node.js and web platforms.

    2k GitHub stars~2.8k tokensUpdated yesterday
    Auto-check passed
  • Debug Php Wasm Side Modules

    WordPress/wordpress-playground

    Debug WASM side modules (dynamic PHP extensions) including dlopen failures, SIDEMODULE loading, JSPI suspension crashes in extensions, C++ weak symbol issues, and extension runtime errors.

    2k GitHub stars~2.5k tokensUpdated yesterday
    Auto-check passed
  • Playground Website Debugging

    WordPress/wordpress-playground

    Debug the WordPress Playground website by running the dev server from source and interacting with it via Playwright MCP.

    2k GitHub stars~1.9k tokensUpdated yesterday
    Auto-check passed

Categories

Questions about Debug Php Wasm Main Module

What does Debug Php Wasm Main Module do?

Debug PHP.wasm main module crashes including Asyncify errors (unreachable, memory access out of bounds), JSPI errors (SuspendError, trying to suspend JS frames), WASM memory growth bugs, and runtime…. Debug Php Wasm Main Module is an agent skill from WordPress/wordpress-playground.wasm main module crashes including Asyncify errors (unreachable, memory access out of bounds), JSPI errors (SuspendError, trying to suspend JS frames), WASM memory growth bugs, and runtime traps.

When should I use Debug Php Wasm Main Module?

Debug Php Wasm Main Module fits situations like: investigating RuntimeError; signature mismatch; other WASM-related crashes in the main PHP binary.

How do I install Debug Php Wasm Main Module in Claude Code?

Run `npx skills add WordPress/wordpress-playground --skill debug-php-wasm-main-module -a claude-code`. Or copy the skill folder (.agents/skills/debug-php-wasm-main-module in WordPress/wordpress-playground) into .claude/skills/debug-php-wasm-main-module in your project. Claude Code loads it when a task matches its description.

How do I install Debug Php Wasm Main Module in Codex?

Run `npx skills add WordPress/wordpress-playground --skill debug-php-wasm-main-module -a codex`. Or copy the skill folder (.agents/skills/debug-php-wasm-main-module in WordPress/wordpress-playground) into .agents/skills/debug-php-wasm-main-module in your project. Codex loads it when a task matches its description.

Can I use Debug Php Wasm Main Module in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add WordPress/wordpress-playground --skill debug-php-wasm-main-module -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/debug-php-wasm-main-module, .gemini/skills/debug-php-wasm-main-module, .github/skills/debug-php-wasm-main-module and .opencode/skills/debug-php-wasm-main-module in your project.

What does Debug Php Wasm Main Module need to run?

Going by SKILL.md and its folder, Debug Php Wasm Main Module needs the command-line tools its instructions call (npx and npm). Our summary lists: Node.js.

Does Debug Php Wasm Main Module access the network?

SKILL.md contains no URLs. Its commands use npx and npm, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Debug Php Wasm Main Module safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Debug Php Wasm Main Module use?

Debug Php Wasm Main Module is published under the GPL-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Debug Php Wasm Main Module use?

About 3.1k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Debug Php Wasm Main Module?

Skills that share tags, products or a category with Debug Php Wasm Main Module: Wp Interactivity API (Automattic/agent-skills, 211 stars), Wp Playground (Automattic/agent-skills, 211 stars), WooCommerce Code Review (woocommerce/woocommerce, 11k stars) and WooCommerce Dev Cycle (woocommerce/woocommerce, 11k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Debug Php Wasm Main Module?

WordPress (a GitHub organization) maintains it in WordPress/wordpress-playground, which has 1,973 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on October 9, 2026.

Source: WordPress/wordpress-playground on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.