GTM Galaxy

Article

Beyond the Sandbox: Local Testing for HubSpot Custom Code Actions

Writing production logic in a browser text area is like trying to fix a watch while wearing oven mitts. When you use the HubSpot Custom Code action editor, you're working in a vacuum: no version control, a cramped IDE, and a "Test" button that rarely accounts for the messy, null-heavy reality of production data.

For RevOps, this isn't just a workflow annoyance. It’s an operational risk. If a custom routing script fails because of a 20.1-second execution time or a stray undefined value in a contact property, leads stop moving. These are often silent failures; you don’t find out the automation is broken until a sales manager asks why their calendar has been empty for three days.

The solution is to treat Custom Code as a real software artifact. By using Claude Code to scaffold a local development harness, you can mock the HubSpot environment, lint for execution limits, and validate output schemas before the code ever touches a live workflow.

The Constraints of the Fenced Playground

HubSpot provides a serverless Node.js environment, but it has rigid boundaries. If you don't account for these locally, your script will die in production:

  • The 20-Second Wall: HubSpot kills any process that exceeds 20 seconds. This is the most common failure point for scripts calling external enrichment APIs or performing complex association lookups.
  • Memory Ceiling: You are capped at 128 MB of RAM. This is usually plenty for logic, but it will choke if you’re processing large JSON arrays or importing heavy libraries.
  • Input/Output Limits: You can only pull in 50 properties as inputs. Strings in the output are capped at 65,000 characters.
  • The Retry Mechanism: HubSpot only retries the action if your code explicitly throws an error. If you catch an error and just log it, HubSpot marks the execution as successful and moves the record to the next step—usually with missing data.

Scaffolding the Harness with Claude Code

A functional local harness requires three components: the logic (handler.js), a mock event (payload.json), and a local execution script (runner.js).

Rather than manually setting up this boilerplate, use Claude Code to scaffold the environment. In your terminal, you can initialize the project with a specific prompt:

claude "Create a HubSpot custom code project. I need a handler.js that exports an async main function, a runner.js to execute it locally with performance timing, and a mock-event.json containing contact properties like email and lifecycle_stage."

Claude will generate a runner.js that simulates the HubSpot execution environment:

const { main } = require('./handler');
const event = require('./mock-event.json');

const callback = (output) => {
  console.log('\n--- Workflow Output ---');
  console.log(JSON.stringify(output, null, 2));
};

(async () => {
  console.log('Starting local execution...');
  console.time('ExecutionTime');
  try {
    await main(event, callback);
  } catch (err) {
    console.error('Execution Failed:', err.message);
  }
  console.timeEnd('ExecutionTime');
})();

This setup gives you immediate feedback on execution time. If your local run takes 18 seconds on a fast fiber connection, it will almost certainly fail the 20-second limit in the cloud.

Mocking the Messy Reality

Scripts rarely fail on "clean" data. They fail when a contact is missing an email, a deal has no associated company, or a custom property contains a string where a number was expected.

Use Claude Code to generate "chaos" payloads. Ask the agent: "Create three variations of mock-event.json: one with all properties as null, one with an invalid email format, and one where the lead_score is a string instead of an integer."

By running your handler.js against these variations locally, you can implement defensive coding—like optional chaining and type validation—before the script ever sees a real lead.

Validating the Downstream Schema

A frequent frustration in HubSpot is the "missing variable" error. You write code that works, but the next workflow step can't find the data. This happens because the keys in your callback object must exactly match the "Data outputs" you manually define in the HubSpot UI.

Claude Code can act as a schema validator. After you've written your logic, use the CLI to check alignment:

claude "List every key returned in the callback of handler.js and check if any values might exceed the 65,000 character limit."

If you're trying to pass a massive JSON blob of enrichment data to a single HubSpot property, Claude can help you write the logic to truncate the data or split it into multiple fields, ensuring the workflow doesn't truncate your data mid-stream.

Managing Dependencies

HubSpot supports a limited set of libraries in its Node.js environment. A common mistake is developing locally with a library like lodash or moment, only to find the script won't compile in HubSpot.

When writing logic with Claude Code, enforce environment constraints in your prompts:

"Refactor this mapping logic to use native JavaScript array methods instead of external libraries, as this will run in a constrained HubSpot Node.js 18 environment."

When to Stay in the Browser

If your task is trivial—capitalizing a name or calculating a simple date offset—setting up a local harness is overkill. The HubSpot web editor is sufficient for single-purpose transformations.

However, the moment your code involves external API calls, complex loops, or multi-branch logic, the browser editor becomes a liability. Local execution won't perfectly replicate network latency between HubSpot's servers and your API endpoints, but it catches logic errors and schema mismatches that the browser editor simply cannot see.

Moving your code into a local IDE and using Claude Code as a technical partner is a shift toward GTM Engineering. It transforms revenue automation from a collection of fragile scripts into a disciplined, testable system.

— C.B.