HCC Docs

During a deploy

A call finishes on the code it started on. hcc deploy writes each generator flow and the local files it imports to .hcc/versions/<flow>@<version>.js, next to hcc.gen.ts, and the version is a hash of that bundle. Commit them like migrations, and don't edit them.

hcc.gen.ts imports the current version from its file and loads older ones when a call needs them, and handler runs each event on the version that wrote the call's history. Deploying the same code twice writes nothing, on any line endings.

What's frozen

  • Your code is. npm and workspace packages stay normal imports, so an upgrade reaches old versions too, and so does a change to your database or APIs.
  • Each version has its own module state, so a DB pool at module scope is one more per version, once a call has loaded it.
  • Tools. A pinned call runs its own version's tool handlers. An agent's tool list on the platform follows the active version, so a tool only a newer version has answers the agent { error }, and the call stays where it is.
  • hcc dev runs from a bundle as well, so what works there works after a deploy. Each save replaces its last file, and hcc prune clears one left by a crash. A flow with its own version needs a new one when you change a file that's already committed.

Share state with a flow

A flow has its own copy of your local files, so state your server sets up doesn't reach it. If a flow reads getDb() from ./config.ts and your server calls setDb() at startup, the flow throws db not set, on its first call, because the server set its own copy. An in-memory cache the app also writes to is worse: nothing throws, and each side reads its own.

Share state in one of two ways:

// 1. Through the handler. A Worker's bindings already arrive this way.
export default handler(registry, { env: { ...process.env, db } }); // c.env.db in a flow, ctx.env.db in a tool

// 2. From a package, npm or workspace: every version imports the same copy.
import { db } from '@acme/db';

Keep your build working

  • A flow must bundle. hcc deploy refuses dynamic imports, import.meta, __dirname, require and non-code assets in what a flow reaches, and says where. A build that only copies compiled output has to copy .hcc as well.
  • Aliases go in your tsconfig. A paths entry is followed and frozen in. An alias only your bundler knows, like Vite's @/helper, is refused, because that code would be neither frozen nor hashed. So is a paths entry that bundles @hcc/sdk itself in.
  • Your bundler reads every kept version, so one that imports a package you've uninstalled fails the build for all of them. hcc deploy --check finds it first and names the version and the package: install the package again, or run hcc prune if no call needs that version.
  • A kept version that throws as it loads doesn't stop anything: it's logged, and only its calls are affected.

Clean up old versions

Run hcc prune now and then, with a live key, and commit what it removes. It deploys nothing, and keeps what live and test calls are on, and what's active for a flow still in your code.

  • A flow you've removed from your code holds its file only while a call is on it.
  • If it can't ask the API, it removes nothing.
  • hcc deploy --live removes the same way, but in CI that's on a machine that goes away. A test deploy never removes anything.

When the server doesn't have the version

This happens with a deleted file, a server not yet rolled, a save under hcc dev, or a version that throws as it loads. The current code then tries to replay the call from what the old one recorded. This can do real harm, so hcc deploy --check is worth running in CI.

What changedWhat happens
Wording, or adding or removing a say, play or pause: those aren't answersIt warns, and the call carries on
The steps the call has been through: a gather becoming a c.run, one added before where the call isHistoryMismatch goes to the current code's onError with the code history_mismatch, otherwise to the Default flow. It's on the call's timeline. At a hangup, the onHangup hook still runs and finally doesn't
The meaning, with the same stepsNot detected, and it can run a real effect

Say v1 recorded c.run('identity') as { verified: false }, and v2 has the same steps but tests !check.failed: the unverified caller passes, and the next c.run('refund') really runs, with only the warning in your log. Swapped menu keys are the mild version of this.

--check fails on a call in the live environment whose version has no file. For a test call it only warns, because a version from an hcc dev save never had one.

During a rolling deploy, a call that started on a new server and reaches an old one is in this case, so ship quickly or drain.