Node SDK
Guard coding-agent runs with one SDK call.
Use @availsync/node before Codex, Claude, Cursor, MCP clients, cron jobs, deploy scripts, or server automations touch a protected repo.
Install
npm install @availsync/nodeEnvironment
AVAILSYNC_API_KEY=avs_live_...
AVAILSYNC_AGENT_ID=agent_uuid
AVAILSYNC_API_URL=https://availsync.devRecommended helper
Use withClaim first
Default to repo-level resources. In enforce mode, blocked agents get skip_run. Observe mode currently applies to the Node SDK, MCP work tools, and direct calls to /v1/work/run/start. In observe mode, the callback still runs and receives shadow metadata.
Basic guarded run
Use the SDK helper when your app should run work only after Availsync claims the repo.
import { createAvailsyncClient } from '@availsync/node';
const availsync = createAvailsyncClient({
apiKey: process.env.AVAILSYNC_API_KEY!,
agentId: process.env.AVAILSYNC_AGENT_ID!,
baseUrl: process.env.AVAILSYNC_API_URL,
});
const result = await availsync.work.withClaim(
'repo:owner/repo',
{
reason: 'coding-agent run',
durationMinutes: 45,
autoExtendIntervalMs: (45 - 5) * 60_000,
autoExtendDurationMinutes: 45,
autoExtendWorkingSet: {
resource: 'repo:owner/repo',
mode: 'edit',
paths: ['frontend/components/Hero.tsx', 'frontend/app/page.tsx'],
leaseSeconds: 600,
},
},
async ({ claim, shadow }) => {
if (shadow.wouldHaveBlocked) {
console.warn('Observe-only: Availsync would have skipped this run.');
}
await claim?.updateWorkingSet({
resource: 'repo:owner/repo',
mode: 'edit',
paths: ['frontend/components/Hero.tsx', 'frontend/app/page.tsx'],
leaseSeconds: 600,
});
await doWork();
},
);
if (result.action === 'skip_run') {
console.log(result.reason);
}Scheduled Codex run
Wrap a scheduled Codex task so it exits cleanly when another agent owns the repo.
import { createAvailsyncClient } from '@availsync/node';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
const availsync = createAvailsyncClient({
apiKey: process.env.AVAILSYNC_API_KEY!,
agentId: 'AGENT_ID',
baseUrl: process.env.AVAILSYNC_API_URL,
});
const result = await availsync.work.withClaim(
'repo:owner/repo',
{
reason: 'scheduled Codex automation',
durationMinutes: 45,
idempotencyKey: `codex-owner-repo-${new Date().toISOString().slice(0, 13)}`,
},
async ({ shadow }) => {
if (shadow.wouldHaveBlocked) {
console.warn('Observe-only: Availsync would have skipped this Codex run.');
}
const child = spawn('codex', ['exec', 'run the scheduled maintenance task'], { stdio: 'inherit' });
const [code] = await once(child, 'exit');
if (code !== 0) throw new Error(`codex exited with ${code}`);
},
);
if (result.action === 'skip_run') process.exit(0);GitHub Actions step
Install the SDK in your automation project and use GitHub run id as the idempotency key.
- name: Run guarded agent task
env:
AVAILSYNC_API_KEY: ${{ secrets.AVAILSYNC_API_KEY }}
AVAILSYNC_AGENT_ID: AGENT_ID
AVAILSYNC_API_URL: https://availsync.dev
run: |
npm install @availsync/node
node ./scripts/guarded-agent-run.mjs
# scripts/guarded-agent-run.mjs
import { createAvailsyncClient } from '@availsync/node';
const availsync = createAvailsyncClient({
apiKey: process.env.AVAILSYNC_API_KEY,
agentId: process.env.AVAILSYNC_AGENT_ID,
baseUrl: process.env.AVAILSYNC_API_URL,
});
const result = await availsync.work.withClaim(
'repo:owner/repo',
{
reason: 'GitHub Actions coding automation',
idempotencyKey: `github-${process.env.GITHUB_RUN_ID}`,
durationMinutes: 45,
autoExtendIntervalMs: (45 - 5) * 60_000,
autoExtendDurationMinutes: 45,
},
async ({ shadow }) => {
if (shadow.wouldHaveBlocked) {
console.warn('Observe-only: Availsync would have skipped this run.');
}
// Run the coding agent command here.
},
);
if (result.action === 'skip_run') process.exit(0);Manual long-running flow
Use start, extend, and finish directly when you need more control than withClaim.
const started = await availsync.work.start({
resource: 'repo:owner/repo',
reason: 'long-running agent run',
durationMinutes: 45,
idempotencyKey: `agent-owner-repo-${Date.now()}`,
});
if (started.action === 'skip_run') {
console.log(started.reason);
process.exit(0);
}
if (started.shadow_mode && started.would_have_blocked) {
console.warn('Observe-only: Availsync would have skipped this run.');
}
let outcome = 'finished';
try {
if (started.claim) {
// Extend before expiry while the long-running task is still active.
await availsync.work.extend(started.claim.id, { durationMinutes: 45 });
}
await doWork();
} catch (error) {
outcome = 'error';
throw error;
} finally {
if (started.claim) {
await availsync.work.finish(started.claim.id, { outcome });
}
}API surface
Methods
createAvailsyncClient({ apiKey, agentId, baseUrl? })Creates a Node 18+ client. The API key belongs to one agent and should only live in server, CI, MCP, or automation env vars.
work.withClaim(resource, options, fn)Recommended helper. Starts a guarded run, runs your callback only when action is proceed, and finishes the claim in finally.
work.start({ resource, durationMinutes?, reason?, idempotencyKey?, metadata? })Low-level start call for custom wrappers. Returns proceed, skip_run, or observe-mode proceed with shadow metadata.
work.check({ resource, durationMinutes?, reason?, metadata? })Non-mutating preview for custom flows. It tells you whether the same resource would be available, without creating a run claim.
work.get(claimId)Fetches one claim by id so a resumed automation can verify the current status, expiry, and renewal count before continuing.
work.list({ resource?, status?, limit?, offset? })Lists claims with pagination. Active claims include their current file working set so dashboards and agents can see what is being edited.
work.extend(claimId, { durationMinutes?, workingSet? })Extends an active lease when a long-running agent needs more time. Pass workingSet to refresh file claims and detect newly blocked paths before continuing.
claim.updateWorkingSet({ paths, mode?, resource?, leaseSeconds? })Advanced file-aware coordination. Declare the files this active run is editing or reviewing. Use resource: "repo:owner/repo" when multiple project-level claims should coordinate on the same repo paths.
work.finish(claimId, { outcome?, reason?, metadata? })Releases the claim after success, failure, or no-op. Repeated finish calls are safe through the run API.
Runtime outcomes
What to handle
proceedThe agent may run. In enforce mode this usually includes a claim that should be extended or finished.
skip_runAnother active claim owns the same resource. Exit cleanly and report the reason.
observe modeThe agent proceeds with claim: null. Availsync records whether it would have blocked the run.
finishErrorIf work succeeds but final cleanup fails, withClaim returns the successful result with finishError instead of throwing away the result.
autoExtendErrorIf automatic lease extension fails while work continues, withClaim returns the successful result with autoExtendError so cleanup issues stay visible.
AvailsyncErrorAuth, validation, plan-limit, network, and unexpected API failures throw a typed error without exposing secrets.
Package status
The public SDK installs with npm install @availsync/node. Pin an exact version for production automations when you need repeatable installs across CI runs.

