Common Pitfalls to Avoid When Building OpenAI Plugins: A Complete Guide

Always validate your manifest structure, await every asynchronous call, return all mutated node IDs, and handle rotating OAuth tokens to prevent silent failures and runtime errors in OpenAI plugins.

OpenAI plugins are built as Codex-plugin bundles within the openai/plugins repository, requiring strict adherence to manifest validation, skill prerequisites, and runtime constraints. Each plugin lives under plugins/<name>/ and contains a manifest (.codex-plugin/plugin.json), optional skills, agents, and commands. Understanding the architectural traps documented across the Figma, Zoom, and Base44 implementations prevents hard-to-debug failures.

Manifest and Entry Point Validation

The Codex runtime validates the plugin manifest strictly. A missing file or incorrect path stops the plugin from loading entirely.

Misconfigured Plugin.json Files

Forgetting the required .codex-plugin/plugin.json or mis-naming the skills/ folder in the manifest will prevent the plugin from loading. The Codex runtime expects the manifest exactly at plugins/<name>/.codex-plugin/plugin.json.

When referencing the skills folder, always use a trailing slash: "skills": "./skills/". The Zoom plugin provides a correct example in its manifest at plugins/zoom/.codex-plugin/plugin.json.

Skill Execution and State Management

Skills are defined in markdown files (typically SKILL.md) that document public APIs, prerequisites, and gotchas. The runtime executes skill code inside a sandbox where state is persisted only through explicit returns.

Missing Skill Prerequisites

Calling a skill like use_figma without first loading its prerequisite skill figma-use causes silent failures. The figma-use skill depends on global setup for API typings and page-state handling. Always load the prerequisite skill first, as documented in plugins/figma/skills/figma-use/SKILL.md.

Async Handling Errors

Omitting await on Promise-returning calls (e.g., figma.loadFontAsync, figma.setCurrentPageAsync) produces race conditions. The helper scripts run in an async wrapper; un-awaited Promises fire-and-forget, causing missing changes. The skill documentation explicitly requires you to “await every Promise” as noted in the figma-use rules.

Node-ID Management Failures

Not returning created or mutated node IDs from use_figma breaks subsequent calls. The Codex harness only receives data that a skill returns; without IDs, later calls cannot reference newly created objects. Always return the specific structure:

{
  "createdNodeIds": [...],
  "mutatedNodeIds": [...]
}

This requirement is documented in plugins/figma/skills/figma-use/SKILL.md under section 1.5.

Page Context Violations

Switching pages multiple times inside a single use_figma script causes unnecessary reloads and triggers the error “Setting figma.currentPage is not supported”. Each script reloads the page once. Call await figma.setCurrentPageAsync(page) once per script and split multi-page work into separate parallel use_figma calls.

OAuth and API Integration Pitfalls

OAuth flows and REST API interactions present specific failure modes around token lifecycle and pagination.

Token Lifecycle Assumptions

Assuming refresh tokens remain stable causes authentication failures. Some providers, including Zoom and Google, rotate refresh tokens on each use, invalidating the previous token. Implement token rotation handling as described in plugins/zoom/skills/oauth/references/full-guide.md section 1.8.

import requests, os

TOKEN_URL = "https://zoom.us/oauth/token"
CLIENT_ID = os.getenv("ZOOM_CLIENT_ID")
CLIENT_SECRET = os.getenv("ZOOM_CLIENT_SECRET")
REFRESH_TOKEN = os.getenv("ZOOM_REFRESH_TOKEN")

def refresh_access():
    resp = requests.post(
        TOKEN_URL,
        auth=(CLIENT_ID, CLIENT_SECRET),
        data={"grant_type": "refresh_token", "refresh_token": REFRESH_TOKEN},
    )
    data = resp.json()
    # Zoom returns a new refresh token each time

    os.environ["ZOOM_REFRESH_TOKEN"] = data["refresh_token"]
    return data["access_token"]

Redirect URI Mismatches

Deploying a plugin with a callback URL that differs from the one registered in the provider console causes OAuth flows to reject the request before any token exchange occurs. Verify the exact URI in the provider's developer console and keep it in sync with /.app.json. See plugins/zoom/skills/oauth/troubleshooting/redirect-uri-issues.md for debugging steps.

Rate Limits and Pagination

Ignoring pagination tokens or exceeding API rate limits results in incomplete data or throttled requests. Many services return partial results and enforce limits per minute. Check the next_page_token in responses and respect Retry-After headers when receiving 429 status codes.

import time, requests, os

def fetch_all(endpoint):
    token = os.getenv("ZOOM_ACCESS_TOKEN")
    url = f"https://api.zoom.us/v2/{endpoint}"
    results = []
    while url:
        resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})
        if resp.status_code == 429:
            retry = int(resp.headers.get("Retry-After", "1"))
            time.sleep(retry)
            continue
        data = resp.json()
        results.extend(data.get("users", []))
        next_token = data.get("next_page_token")
        url = f"https://api.zoom.us/v2/{endpoint}?next_page_token={next_token}" if next_token else None
    return results

This pattern follows the guidance in plugins/zoom/skills/rest-api/troubleshooting/common-issues.md.

Runtime and Environment Constraints

Plugins execute across multiple runtimes (Python helpers, JavaScript SDKs, OAuth flows), requiring careful handling of file systems and platform capabilities.

File System Side Effects

Creating folders or files without checking existence causes crashes on first run or in CI environments where directories already exist. Guard against existing paths or use os.makedirs(..., exist_ok=True) in Python helpers. The base44-cli skill documents this in plugins/base44/skills/base44-cli/SKILL.md.

Platform-Specific API Usage

Using APIs available only in one environment (e.g., figma.createPage() in FigJam) triggers "not supported" errors. Detect the editor type using figma.editorType and call only supported methods. The figma-use skill documentation lists design-only versus Slides-only APIs in plugins/figma/skills/figma-use/SKILL.md.

Environment Variable and Dependency Issues

Hard-coding API keys or forgetting to add them to a .env file causes runtime errors, as secrets are stripped from the repository. Create a .env placeholder (API_KEY=) and document it in the plugin's README. Additionally, importing deprecated sub-modules (e.g., @wix/data instead of @wix/wix-data-items-sdk) causes build failures. Follow upgrade notes in skill documentation, such as those in plugins/wix/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md.

Summary

  • Validate manifest structure: Keep .codex-plugin/plugin.json at the root and reference skills/ with a trailing slash.
  • Await every async call: Un-awaited Promises in JavaScript skills create race conditions and missing state changes.
  • Return all node IDs: Always include createdNodeIds and mutatedNodeIds in return values for Figma skills.
  • Handle OAuth rotation: Store new refresh tokens immediately, as providers like Zoom invalidate old ones after use.
  • Respect rate limits: Check next_page_token for pagination and Retry-After headers for throttling.
  • Guard file operations: Use exist_ok=True or existence checks before creating directories.
  • Check platform APIs: Verify figma.editorType before calling environment-specific methods.

Frequently Asked Questions

What happens if I forget to return node IDs in a Figma skill?

The Codex harness cannot track objects created during skill execution. Without returning createdNodeIds or mutatedNodeIds in the skill response, subsequent commands cannot reference those objects, effectively making them unreachable in the conversation context. Always return these IDs as specified in plugins/figma/skills/figma-use/SKILL.md.

Why does my Zoom plugin fail after the first OAuth refresh?

Zoom rotates refresh tokens on every use, invalidating the previous token. If your code stores the original refresh token and reuses it, subsequent authentication attempts will fail. You must capture and store the new refresh_token from each token response, as shown in the Zoom OAuth reference at plugins/zoom/skills/oauth/references/full-guide.md.

How do I handle rate limiting in OpenAI plugin API calls?

Implement exponential backoff using the Retry-After header when receiving 429 status codes. For paginated endpoints, always check for next_page_token in the response and continue fetching until no token remains. This pattern is documented in plugins/zoom/skills/rest-api/troubleshooting/common-issues.md.

Can I use FigJam-specific APIs in a standard Figma design file?

No. APIs like figma.createPage() are restricted to specific editor types. You must check figma.editorType before calling environment-specific methods, or the runtime will throw a "not supported" error. Consult the editor mode documentation in plugins/figma/skills/figma-use/SKILL.md for the API compatibility matrix.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →