ax-check.com
by Gauge
S
Statsig
statsig.com
ax-check.com
Checked 6 October 2026
B
65/100
Agents need your login
Statsig quickstart and pricing are clear, but llms.txt is missing
16 of 23 checks passed: install steps, code samples, and pricing are all stated plainly. No llms.txt index exists (404), so doc discovery relies on normal navigation.
3 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no statsig.com account. Recorded 6 October 2026 on Pi.
3 recorded · 2m 54s
2m 54s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
The user wants a simple Statsig example. Let me first check the repo, then think about Statsig. Statsig requires an API key from a hosted account — I can't sign up. So it's likely blocked on credentials. Let me check the environment for any keys.
Let me inspect the repo first.
I'll start by checking what's in the repo and whether any Statsig credentials are available.
ls -la /sandbox/repo && echo "---" && cat /sandbox/repo/README* 2>/dev/null | head -50
total 16
total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Oct 6 19:58 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore ---
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-10-06T19:58:18.898Z",
"name": "bash",
"turn": 1,
"input": {
"command": "ls -la /sandbox/repo && echo \"---\" && cat /sandbox/repo/README* 2>/dev/null | head -50"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8c0c822b604cbef2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 6,
"ts": "2026-10-06T19:58:18.898Z",
"turn": 1,
"output": {
"content": [
{
"text": "total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Oct 6 19:58 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n---\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8c0c822b604cbef2",
"is_error": false,
"tool_kind": "shell"
}env | grep -iE 'statsig|feature|flag' ; echo "--- env keys count:"; env | wc -l; echo "--- dotfiles:"; ls -la ~ 2>/dev/null | head -20
--- env keys count:
--- env keys count: 40 --- dotfiles: total 28 drwxr-xr-x 7 root root 4096 Oct 6 19:58 . drwxr-xr-x 22 root root 4096 Sep 18 07:59 .. drwxr-xr-x 3 root root 4096 Oct 6 19:58 .cache drwxr-xr-x 3 root root 4096 Oct 6 19:58 .gauge drwxr-xr-x 3 root root 4096 Oct 6 19:58 .pi drwxrwxrwt 2 root root 4096 Sep 18 08:00 .tmp drwxr-xr-x 3 root root 4096 Sep 18 08:00 repo
Recorded tool call
{
"type": "tool_call",
"seq": 5,
"ts": "2026-10-06T19:58:18.898Z",
"name": "bash",
"turn": 1,
"input": {
"command": "env | grep -iE 'statsig|feature|flag' ; echo \"--- env keys count:\"; env | wc -l; echo \"--- dotfiles:\"; ls -la ~ 2>/dev/null | head -20"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b7f42cee750558a2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 7,
"ts": "2026-10-06T19:58:18.898Z",
"turn": 1,
"output": {
"content": [
{
"text": "--- env keys count:\n40\n--- dotfiles:\ntotal 28\ndrwxr-xr-x 7 root root 4096 Oct 6 19:58 .\ndrwxr-xr-x 22 root root 4096 Sep 18 07:59 ..\ndrwxr-xr-x 3 root root 4096 Oct 6 19:58 .cache\ndrwxr-xr-x 3 root root 4096 Oct 6 19:58 .gauge\ndrwxr-xr-x 3 root root 4096 Oct 6 19:58 .pi\ndrwxrwxrwt 2 root root 4096 Sep 18 08:00 .tmp\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 repo\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b7f42cee750558a2",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Empty repo, no Statsig keys. Let me check network access and whether the Statsig SDK can be installed. Statsig has a public SDK; but it needs a server-side secret key to fetch gates. Without credentials, I can't do a real end-to-end example. Let me check network availability to see if I can at least install the SDK.
Let me check python/node availability and network.
Empty repo, no Statsig credentials in the environment. Let me check network access and available runtimes.
cd /tmp && node -v; python3 -V; echo "--- npm registry reachability:"; timeout 20 curl -s -o /dev/null -w "%{http_code}\n" https://registry.npmjs.org/statsig-node-core; echo "--- statsig api reachability:"; timeout 20 curl -s -o /dev/null -w "%{http_code}\n" https://featureassets.org/v1/check_gatev22.23.2
v22.23.2 Python 3.12.3 --- npm registry reachability: 200 --- statsig api reachability: 403
Recorded tool call
{
"type": "tool_call",
"seq": 11,
"ts": "2026-10-06T19:58:25.035Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && node -v; python3 -V; echo \"--- npm registry reachability:\"; timeout 20 curl -s -o /dev/null -w \"%{http_code}\\n\" https://registry.npmjs.org/statsig-node-core; echo \"--- statsig api reachability:\"; timeout 20 curl -s -o /dev/null -w \"%{http_code}\\n\" https://featureassets.org/v1/check_gate"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8d1bb34503d9f780",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 14,
"ts": "2026-10-06T19:58:25.708Z",
"turn": 1,
"output": {
"content": [
{
"text": "v22.23.2\nPython 3.12.3\n--- npm registry reachability:\n200\n--- statsig api reachability:\n403\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8d1bb34503d9f780",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && timeout 25 curl -s https://api.statsig.com/v1/rulesets -H "STATSIG-SERVER-SECRET: secret-invalid-test" -w "\nHTTP:%{http_code}\n" | tail -3RBAC: access denied
RBAC: access denied HTTP:403
Recorded tool call
{
"type": "tool_call",
"seq": 12,
"ts": "2026-10-06T19:58:25.035Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 25 curl -s https://api.statsig.com/v1/rulesets -H \"STATSIG-SERVER-SECRET: secret-invalid-test\" -w \"\\nHTTP:%{http_code}\\n\" | tail -3"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-876220321ca6165a",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 13,
"ts": "2026-10-06T19:58:25.415Z",
"turn": 1,
"output": {
"content": [
{
"text": "RBAC: access denied\nHTTP:403\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-876220321ca6165a",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Network works, npm registry works, Statsig endpoints reachable (403 due to invalid key — expected). So I can install the SDK and build a working example. But without a real secret key I cannot demonstrate a live flag evaluation.
Can I sign up for a Statsig account programmatically? No — signup requires email verification, a UI, org creation. That's a blocker for a live end-to-end run. But I can still build the example with the SDK and demonstrate the offline/bootstrap path — Statsig's Node SDK supports "bootstrap" values, meaning you can inject gate values without a network call. Actually, statsig-node-core supports initialization with a data adapter, and there's also Statsig.initialize with network config. There's a way to run offline: statsig-node has an offline mode / DataAdapter where you can supply local values.
Let me be honest about the situation: The Statsig workflow requires creating an account in a web console to get a secret key. That's a human step. I cannot do the live part. But I can:
- Install the SDK.
- Write a runnable example that uses a bootstrap/offline data adapter so it runs without a key, and clearly shows where the real key goes.
- Verify it runs.
Let me check the actual statsig-node-core API to make sure I get this right. Let me install and inspect.
Let me install statsig-node-core in the repo.
Network and npm work; the Statsig endpoints respond (403 = invalid key, as expected). Let me install the SDK and inspect its real API.
cd /sandbox/repo && npm init -y >/dev/null 2>&1 && timeout 120 npm install statsig-node-core 2>&1 | tail -5; echo "---"; node -e "console.log(require('/sandbox/repo/node_modules/statsig-node-core/package.json').version)"Failednpm error code ENOVERSIONS
npm error code ENOVERSIONS
npm error No versions available for statsig-node-core
npm error A complete log of this run can be found in: /sandbox/.npm/_logs/2026-10-06T19_58_36_319Z-debug-0.log
---
node:internal/modules/cjs/loader:1433
throw err;
^
Error: Cannot find module '/sandbox/repo/node_modules/statsig-node-core/package.json'
Require stack:
- /sandbox/repo/[eval]
at Function._resolveFilename (node:internal/modules/cjs/loader:1430:15)
at defaultResolveImpl (node:internal/modules/cjs/loader:1040:19)
at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1045:22)
at Function._load (node:internal/modules/cjs/loader:1216:25)
at wrapModuleLoad (node:internal/modules/cjs/loader:254:19)
at Module.require (node:internal/modules/cjs/loader:1527:12)
at require (node:internal/modules/helpers:147:16)
at [eval]:1:13
at runScriptInThisContext (node:internal/vm:209:10)
at node:internal/process/execution:446:12 {
code: 'MODULE_NOT_FOUND',
requireStack: [ '/sandbox/repo/[eval]' ]
}
Node.js v22.23.2
Command exited with code 1Recorded tool call
{
"type": "tool_call",
"seq": 18,
"ts": "2026-10-06T19:58:36.138Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && npm init -y >/dev/null 2>&1 && timeout 120 npm install statsig-node-core 2>&1 | tail -5; echo \"---\"; node -e \"console.log(require('/sandbox/repo/node_modules/statsig-node-core/package.json').version)\""
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-9088d151d5225363",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 19,
"ts": "2026-10-06T19:58:37.920Z",
"turn": 1,
"output": {
"content": [
{
"text": "npm error code ENOVERSIONS\nnpm error No versions available for statsig-node-core\nnpm error A complete log of this run can be found in: /sandbox/.npm/_logs/2026-10-06T19_58_36_319Z-debug-0.log\n---\nnode:internal/modules/cjs/loader:1433\n throw err;\n ^\n\nError: Cannot find module '/sandbox/repo/node_modules/statsig-node-core/package.json'\nRequire stack:\n- /sandbox/repo/[eval]\n at Function._resolveFilename (node:internal/modules/cjs/loader:1430:15)\n at defaultResolveImpl (node:internal/modules/cjs/loader:1040:19)\n at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1045:22)\n at Function._load (node:internal/modules/cjs/loader:1216:25)\n at wrapModuleLoad (node:internal/modules/cjs/loader:254:19)\n at Module.require (node:internal/modules/cjs/loader:1527:12)\n at require (node:internal/modules/helpers:147:16)\n at [eval]:1:13\n at runScriptInThisContext (node:internal/vm:209:10)\n at node:internal/process/execution:446:12 {\n code: 'MODULE_NOT_FOUND',\n requireStack: [ '/sandbox/repo/[eval]' ]\n}\n\nNode.js v22.23.2\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-9088d151d5225363",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
The package name is wrong. Let me check what Statsig packages exist. @statsig/node-sdk is the newer one. Let me query the registry.
Wrong package name. Let me find the correct current package.
cd /tmp && for p in @statsig/node-sdk statsig-node statsig-node-core @statsig/js-client; do echo "== $p"; timeout 20 curl -s "https://registry.npmjs.org/$(echo $p|sed 's|/|%2f|')" | python3 -c "
import sys,json
d=json.load(sys.stdin)
if 'dist-tags' in d: print('latest:',d['dist-tags'].get('latest'),'| deprecated?',d.get('versions',{}).get(d['dist-tags']['latest'],{}).get('deprecated'))
else: print('ERR',str(d)[:100])
"; done== @statsig/node-sdk
== @statsig/node-sdk
ERR {'error': 'Not found'}
== statsig-node
latest: 6.5.2 | deprecated? None
== statsig-node-core
ERR {'_id': 'statsig-node-core', 'name': 'statsig-node-core', 'time': {'created': '2025-01-31T01:22:58.4
== @statsig/js-client
latest: 3.33.5 | deprecated? None
Recorded tool call
{
"type": "tool_call",
"seq": 23,
"ts": "2026-10-06T19:58:42.529Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && for p in @statsig/node-sdk statsig-node statsig-node-core @statsig/js-client; do echo \"== $p\"; timeout 20 curl -s \"https://registry.npmjs.org/$(echo $p|sed 's|/|%2f|')\" | python3 -c \"\nimport sys,json\nd=json.load(sys.stdin)\nif 'dist-tags' in d: print('latest:',d['dist-tags'].get('latest'),'| deprecated?',d.get('versions',{}).get(d['dist-tags']['latest'],{}).get('deprecated'))\nelse: print('ERR',str(d)[:100])\n\"; done"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-a5f01e9c86246bb8",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 24,
"ts": "2026-10-06T19:58:44.830Z",
"turn": 1,
"output": {
"content": [
{
"text": "== @statsig/node-sdk\nERR {'error': 'Not found'}\n== statsig-node\nlatest: 6.5.2 | deprecated? None\n== statsig-node-core\nERR {'_id': 'statsig-node-core', 'name': 'statsig-node-core', 'time': {'created': '2025-01-31T01:22:58.4\n== @statsig/js-client\nlatest: 3.33.5 | deprecated? None\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-a5f01e9c86246bb8",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
So statsig-node v6.5.2 is the current Node server SDK. Let me install it and inspect its API.
Note: I should verify whether statsig-node v6 supports offline/bootstrap. Let me install and inspect exports.
statsig-node v6.5.2 is the current server SDK. Installing and inspecting its API.
cd /sandbox/repo && timeout 180 npm install statsig-node 2>&1 | tail -3; echo "=== version:"; node -e "console.log(require('statsig-node/package.json').version)"run `npm fund` for details
run `npm fund` for details found 0 vulnerabilities === version: 6.5.2
Recorded tool call
{
"type": "tool_call",
"seq": 28,
"ts": "2026-10-06T19:58:48.536Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 180 npm install statsig-node 2>&1 | tail -3; echo \"=== version:\"; node -e \"console.log(require('statsig-node/package.json').version)\""
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-97bf3d9421a0d89e",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 29,
"ts": "2026-10-06T19:58:51.518Z",
"turn": 1,
"output": {
"content": [
{
"text": " run `npm fund` for details\n\nfound 0 vulnerabilities\n=== version:\n6.5.2\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-97bf3d9421a0d89e",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Installed. Now inspect the exports to see what's available for offline/bootstrap initialization.
cd /sandbox/repo && node -e "
const s=require('statsig-node');
console.log(Object.keys(s).sort().join('\n'));
" 2>&1 | head -40DataAdapterKeyPath
DataAdapterKeyPath DynamicConfig Layer StatsigInvalidArgumentError StatsigLocalModeNetworkError StatsigServer StatsigTooManyRequestsError StatsigUninitializedError _enforceServer checkGate checkGateSync checkGateWithExposureLoggingDisabled checkGateWithExposureLoggingDisabledSync clearAllConfigOverrides clearAllGateOverrides clearAllLayerOverrides default flush getAutotuneList getClientInitializeResponse getConfig getConfigSync getConfigWithExposureLoggingDisabled getConfigWithExposureLoggingDisabledSync getDynamicConfigList getExperiment getExperimentLayer getExperimentList getExperimentSync getExperimentWithExposureLoggingDisabled getExperimentWithExposureLoggingDisabledSync getFeatureGate getFeatureGateList getFeatureGateSync getFeatureGateWithExposureLoggingDisabled getLayer getLayerList getLayerSync getLayerWithExposureLoggingDisabled getLayerWithExposureLoggingDisabledSync
Recorded tool call
{
"type": "tool_call",
"seq": 32,
"ts": "2026-10-06T19:58:54.937Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && node -e \"\nconst s=require('statsig-node');\nconsole.log(Object.keys(s).sort().join('\\n'));\n\" 2>&1 | head -40"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-ab3af02260a2486e",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 35,
"ts": "2026-10-06T19:58:54.937Z",
"turn": 1,
"output": {
"content": [
{
"text": "DataAdapterKeyPath\nDynamicConfig\nLayer\nStatsigInvalidArgumentError\nStatsigLocalModeNetworkError\nStatsigServer\nStatsigTooManyRequestsError\nStatsigUninitializedError\n_enforceServer\ncheckGate\ncheckGateSync\ncheckGateWithExposureLoggingDisabled\ncheckGateWithExposureLoggingDisabledSync\nclearAllConfigOverrides\nclearAllGateOverrides\nclearAllLayerOverrides\ndefault\nflush\ngetAutotuneList\ngetClientInitializeResponse\ngetConfig\ngetConfigSync\ngetConfigWithExposureLoggingDisabled\ngetConfigWithExposureLoggingDisabledSync\ngetDynamicConfigList\ngetExperiment\ngetExperimentLayer\ngetExperimentList\ngetExperimentSync\ngetExperimentWithExposureLoggingDisabled\ngetExperimentWithExposureLoggingDisabledSync\ngetFeatureGate\ngetFeatureGateList\ngetFeatureGateSync\ngetFeatureGateWithExposureLoggingDisabled\ngetLayer\ngetLayerList\ngetLayerSync\ngetLayerWithExposureLoggingDisabled\ngetLayerWithExposureLoggingDisabledSync\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-ab3af02260a2486e",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo && ls node_modules/statsig-node/dist/types/ 2>/dev/null; echo "=== DataAdapter / offline refs:"; grep -rlioE 'offline|bootstrap' node_modules/statsig-node/dist/types/ | head -20
=== DataAdapter / offline refs:
=== DataAdapter / offline refs: grep: node_modules/statsig-node/dist/types/: No such file or directory
Recorded tool call
{
"type": "tool_call",
"seq": 33,
"ts": "2026-10-06T19:58:54.937Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && ls node_modules/statsig-node/dist/types/ 2>/dev/null; echo \"=== DataAdapter / offline refs:\"; grep -rlioE 'offline|bootstrap' node_modules/statsig-node/dist/types/ | head -20"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b274d71516c56c52",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 34,
"ts": "2026-10-06T19:58:54.937Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== DataAdapter / offline refs:\ngrep: node_modules/statsig-node/dist/types/: No such file or directory\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b274d71516c56c52",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
There's StatsigLocalModeNetworkError and _enforceServer — interesting, that suggests a local mode exists. Let me look for local mode / data adapter support in the package.
Let me check the exports more and look for DataAdapter, StatsigLocal, overrides.
There's a StatsigLocalModeNetworkError export — suggesting a local mode. Let me investigate.
cd /sandbox/repo && node -e "const s=require('statsig-node');console.log(Object.keys(s).sort().join('\n'))" 2>&1 | tail -30; echo "=== files:"; ls node_modules/statsig-node/dist/ | head; echo "=== local mode refs:"; grep -rlioE 'localMode|DataAdapter|setOverride' node_modules/statsig-node/dist/*.js node_modules/statsig-node/dist/**/*.js 2>/dev/null | headgetExperiment
getExperiment getExperimentLayer getExperimentList getExperimentSync getExperimentWithExposureLoggingDisabled getExperimentWithExposureLoggingDisabledSync getFeatureGate getFeatureGateList getFeatureGateSync getFeatureGateWithExposureLoggingDisabled getLayer getLayerList getLayerSync getLayerWithExposureLoggingDisabled getLayerWithExposureLoggingDisabledSync getUserPersistedValues initialize logEvent logEventObject manuallyLogConfigExposure manuallyLogExperimentExposure manuallyLogGateExposure manuallyLogLayerParameterExposure overrideConfig overrideGate overrideLayer shutdown shutdownAsync syncConfigSpecs syncIdLists === files: ConfigEvaluation.d.ts ConfigEvaluation.js ConfigSpec.d.ts ConfigSpec.js Diagnostics.d.ts Diagnostics.js DynamicConfig.d.ts DynamicConfig.js ErrorBoundary.d.ts ErrorBoundary.js === local mode refs: node_modules/statsig-node/dist/ErrorBoundary.js node_modules/statsig-node/dist/Errors.js node_modules/statsig-node/dist/LogEventProcessor.js node_modules/statsig-node/dist/SpecStore.js node_modules/statsig-node/dist/StatsigOptions.js node_modules/statsig-node/dist/StatsigServer.js node_modules/statsig-node/dist/index.js node_modules/statsig-node/dist/interfaces/IDataAdapter.js node_modules/statsig-node/dist/utils/IDListUtil.js node_modules/statsig-node/dist/utils/StatsigFetcher.js
Recorded tool call
{
"type": "tool_call",
"seq": 39,
"ts": "2026-10-06T19:58:59.170Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && node -e \"const s=require('statsig-node');console.log(Object.keys(s).sort().join('\\n'))\" 2>&1 | tail -30; echo \"=== files:\"; ls node_modules/statsig-node/dist/ | head; echo \"=== local mode refs:\"; grep -rlioE 'localMode|DataAdapter|setOverride' node_modules/statsig-node/dist/*.js node_modules/statsig-node/dist/**/*.js 2>/dev/null | head"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-bc98d5fd1ad016cb",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 40,
"ts": "2026-10-06T19:58:59.170Z",
"turn": 1,
"output": {
"content": [
{
"text": "getExperiment\ngetExperimentLayer\ngetExperimentList\ngetExperimentSync\ngetExperimentWithExposureLoggingDisabled\ngetExperimentWithExposureLoggingDisabledSync\ngetFeatureGate\ngetFeatureGateList\ngetFeatureGateSync\ngetFeatureGateWithExposureLoggingDisabled\ngetLayer\ngetLayerList\ngetLayerSync\ngetLayerWithExposureLoggingDisabled\ngetLayerWithExposureLoggingDisabledSync\ngetUserPersistedValues\ninitialize\nlogEvent\nlogEventObject\nmanuallyLogConfigExposure\nmanuallyLogExperimentExposure\nmanuallyLogGateExposure\nmanuallyLogLayerParameterExposure\noverrideConfig\noverrideGate\noverrideLayer\nshutdown\nshutdownAsync\nsyncConfigSpecs\nsyncIdLists\n=== files:\nConfigEvaluation.d.ts\nConfigEvaluation.js\nConfigSpec.d.ts\nConfigSpec.js\nDiagnostics.d.ts\nDiagnostics.js\nDynamicConfig.d.ts\nDynamicConfig.js\nErrorBoundary.d.ts\nErrorBoundary.js\n=== local mode refs:\nnode_modules/statsig-node/dist/ErrorBoundary.js\nnode_modules/statsig-node/dist/Errors.js\nnode_modules/statsig-node/dist/LogEventProcessor.js\nnode_modules/statsig-node/dist/SpecStore.js\nnode_modules/statsig-node/dist/StatsigOptions.js\nnode_modules/statsig-node/dist/StatsigServer.js\nnode_modules/statsig-node/dist/index.js\nnode_modules/statsig-node/dist/interfaces/IDataAdapter.js\nnode_modules/statsig-node/dist/utils/IDListUtil.js\nnode_modules/statsig-node/dist/utils/StatsigFetcher.js\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-bc98d5fd1ad016cb",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
There's overrideGate, overrideConfig, overrideLayer and an IDataAdapter. And initialize supports options. Let me look at StatsigOptions to see if there's a dataAdapter option and a local mode (no network) option. This would let me build a genuinely runnable example without a real key.
Let me check StatsigOptions.d.ts and IDataAdapter.d.ts.
Good — there's an IDataAdapter and override APIs. Let me check the options for a no-network mode.
cd /sandbox/repo && cat node_modules/statsig-node/dist/StatsigOptions.d.ts | head -80
import DynamicConfig from './DynamicConfig';
import DynamicConfig from './DynamicConfig';
import { FeatureGate } from './FeatureGate';
import { IDataAdapter } from './interfaces/IDataAdapter';
import { IUserPersistentStorage, UserPersistedValues } from './interfaces/IUserPersistentStorage';
import Layer from './Layer';
import LogEvent from './LogEvent';
import { StatsigUser } from './StatsigUser';
import { HashingAlgorithm } from './utils/Hashing';
export type RulesUpdatedCallback = (rulesJSON: string, time: number) => void;
export type RetryBackoffFunc = (retriesRemaining: number) => number;
type StringLiteralOrString<T extends string> = T | (string & {});
export type StatsigEnvironment = {
tier?: StringLiteralOrString<'production' | 'staging' | 'development'>;
[key: string]: string | undefined;
};
export type InitStrategy = 'await' | 'lazy' | 'none';
export interface LoggerInterface {
debug?(message?: any, ...optionalParams: any[]): void;
info?(message?: any, ...optionalParams: any[]): void;
warn(message?: any, ...optionalParams: any[]): void;
error(message?: any, ...optionalParams: any[]): void;
logLevel: 'none' | 'debug' | 'info' | 'warn' | 'error';
}
export type NetworkOverrideFunc = (url: string, params: RequestInit) => Promise<Response>;
export type EvaluationCallbacks = {
gateCallback?: (gate: FeatureGate, user: StatsigUser, event: LogEvent) => void;
dynamicConfigCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void;
experimentCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void;
layerCallback?: (layer: Layer, user: StatsigUser) => void;
layerParamCallback?: (layer: Layer, paramName: string, user: StatsigUser, event: LogEvent) => void;
};
export type ExplicitStatsigOptions = {
api: string;
apiForDownloadConfigSpecs: string;
apiForGetIdLists: string;
fallbackToStatsigAPI: boolean;
networkOverrideFunc: NetworkOverrideFunc | null;
bootstrapValues: string | null;
environment: StatsigEnvironment | null;
rulesUpdatedCallback: RulesUpdatedCallback | null;
logger: LoggerInterface;
localMode: boolean;
initTimeoutMs: number;
dataAdapter: IDataAdapter | null;
rulesetsSyncIntervalMs: number;
idListsSyncIntervalMs: number;
loggingIntervalMs: number;
loggingMaxBufferSize: number;
disableDiagnostics: boolean;
initStrategyForIP3Country: InitStrategy;
initStrategyForIDLists: InitStrategy;
postLogsRetryLimit: number;
postLogsRetryBackoff: RetryBackoffFunc | number;
disableRulesetsSync: boolean;
disableIdListsSync: boolean;
disableAllLogging: boolean;
userPersistentStorage: IUserPersistentStorage | null;
evaluationCallback?: (config: FeatureGate | DynamicConfig | Layer) => void;
evaluationCallbacks?: EvaluationCallbacks;
};
/**
* An object of properties for initializing the sdk with advanced options
*/
export type StatsigOptions = Partial<ExplicitStatsigOptions>;
export declare function OptionsWithDefaults(opts: StatsigOptions): ExplicitStatsigOptions;
export declare function OptionsLoggingCopy(options: Record<string, unknown>): StatsigOptions;
export type PersistentAssignmentOptions = {
enforceTargeting?: boolean;
};
export type CheckGateOptions = CoreApiOptions;
export type GetConfigOptions = CoreApiOptions;
export type GetExperimentOptions = CoreApiOptions & {
userPersistedValues?: UserPersistedValues | null;
persistentAssignmentOptions?: PersistentAssignmentOptions;
};
export type GetLayerOptions = CoreApiOptions & {
userPersistedValues?: UserPersistedValues | null;
persistentAssignmentOptions?: PersistentAssignmentOptions;
};
export type CoreApiOptions = {
Recorded tool call
{
"type": "tool_call",
"seq": 44,
"ts": "2026-10-06T19:59:03.769Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && cat node_modules/statsig-node/dist/StatsigOptions.d.ts | head -80"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b380c3b698d24241",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 46,
"ts": "2026-10-06T19:59:03.769Z",
"turn": 1,
"output": {
"content": [
{
"text": "import DynamicConfig from './DynamicConfig';\nimport { FeatureGate } from './FeatureGate';\nimport { IDataAdapter } from './interfaces/IDataAdapter';\nimport { IUserPersistentStorage, UserPersistedValues } from './interfaces/IUserPersistentStorage';\nimport Layer from './Layer';\nimport LogEvent from './LogEvent';\nimport { StatsigUser } from './StatsigUser';\nimport { HashingAlgorithm } from './utils/Hashing';\nexport type RulesUpdatedCallback = (rulesJSON: string, time: number) => void;\nexport type RetryBackoffFunc = (retriesRemaining: number) => number;\ntype StringLiteralOrString<T extends string> = T | (string & {});\nexport type StatsigEnvironment = {\n tier?: StringLiteralOrString<'production' | 'staging' | 'development'>;\n [key: string]: string | undefined;\n};\nexport type InitStrategy = 'await' | 'lazy' | 'none';\nexport interface LoggerInterface {\n debug?(message?: any, ...optionalParams: any[]): void;\n info?(message?: any, ...optionalParams: any[]): void;\n warn(message?: any, ...optionalParams: any[]): void;\n error(message?: any, ...optionalParams: any[]): void;\n logLevel: 'none' | 'debug' | 'info' | 'warn' | 'error';\n}\nexport type NetworkOverrideFunc = (url: string, params: RequestInit) => Promise<Response>;\nexport type EvaluationCallbacks = {\n gateCallback?: (gate: FeatureGate, user: StatsigUser, event: LogEvent) => void;\n dynamicConfigCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void;\n experimentCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void;\n layerCallback?: (layer: Layer, user: StatsigUser) => void;\n layerParamCallback?: (layer: Layer, paramName: string, user: StatsigUser, event: LogEvent) => void;\n};\nexport type ExplicitStatsigOptions = {\n api: string;\n apiForDownloadConfigSpecs: string;\n apiForGetIdLists: string;\n fallbackToStatsigAPI: boolean;\n networkOverrideFunc: NetworkOverrideFunc | null;\n bootstrapValues: string | null;\n environment: StatsigEnvironment | null;\n rulesUpdatedCallback: RulesUpdatedCallback | null;\n logger: LoggerInterface;\n localMode: boolean;\n initTimeoutMs: number;\n dataAdapter: IDataAdapter | null;\n rulesetsSyncIntervalMs: number;\n idListsSyncIntervalMs: number;\n loggingIntervalMs: number;\n loggingMaxBufferSize: number;\n disableDiagnostics: boolean;\n initStrategyForIP3Country: InitStrategy;\n initStrategyForIDLists: InitStrategy;\n postLogsRetryLimit: number;\n postLogsRetryBackoff: RetryBackoffFunc | number;\n disableRulesetsSync: boolean;\n disableIdListsSync: boolean;\n disableAllLogging: boolean;\n userPersistentStorage: IUserPersistentStorage | null;\n evaluationCallback?: (config: FeatureGate | DynamicConfig | Layer) => void;\n evaluationCallbacks?: EvaluationCallbacks;\n};\n/**\n * An object of properties for initializing the sdk with advanced options\n */\nexport type StatsigOptions = Partial<ExplicitStatsigOptions>;\nexport declare function OptionsWithDefaults(opts: StatsigOptions): ExplicitStatsigOptions;\nexport declare function OptionsLoggingCopy(options: Record<string, unknown>): StatsigOptions;\nexport type PersistentAssignmentOptions = {\n enforceTargeting?: boolean;\n};\nexport type CheckGateOptions = CoreApiOptions;\nexport type GetConfigOptions = CoreApiOptions;\nexport type GetExperimentOptions = CoreApiOptions & {\n userPersistedValues?: UserPersistedValues | null;\n persistentAssignmentOptions?: PersistentAssignmentOptions;\n};\nexport type GetLayerOptions = CoreApiOptions & {\n userPersistedValues?: UserPersistedValues | null;\n persistentAssignmentOptions?: PersistentAssignmentOptions;\n};\nexport type CoreApiOptions = {\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b380c3b698d24241",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo && cat node_modules/statsig-node/dist/interfaces/IDataAdapter.d.ts; echo "=== initialize sig:"; cat node_modules/statsig-node/dist/index.d.ts 2>/dev/null | head -40
export type AdapterResponse = {
export type AdapterResponse = {
result?: string | object;
time?: number;
error?: Error;
};
export declare enum DataAdapterKeyPath {
V1Rulesets = "/v1/download_config_specs",
V2Rulesets = "/v2/download_config_specs",
V1IDLists = "/v1/get_id_lists",
IDList = "id_list"
}
export declare enum CompressFormat {
PlainText = "plain_text",
Gzip = "gzip"
}
export declare function getDataAdapterKey(hashedSDKKey: string, path: DataAdapterKeyPath, format?: CompressFormat, idListName?: string | undefined): string;
/**
* An adapter for implementing custom storage of config specs.
* Useful for backing up data in memory.
* Can also be used to bootstrap Statsig server.
*/
export interface IDataAdapter {
/**
* Returns the data stored for a specific key
* @param key - Key of stored item to fetch
*/
get(key: string): Promise<AdapterResponse>;
/**
* Updates data stored for each key
* @param key - Key of stored item to update
* @param value - New value to store
* @param time - Time of update
*/
set(key: string, value: string, time?: number): Promise<void>;
/**
* Startup tasks to run before any fetch/update calls can be made
*/
initialize(): Promise<void>;
/**
* Cleanup tasks to run when statsig is shutdown
*/
shutdown(): Promise<void>;
/**
* Determines whether the SDK should poll for updates from
* the data adapter for the given key
* @param key - Key of stored item to poll from data adapter
*/
supportsPollingUpdatesFor?(key: DataAdapterKeyPath): boolean;
}
=== initialize sig:
import DynamicConfig from './DynamicConfig';
import { StatsigInvalidArgumentError, StatsigLocalModeNetworkError, StatsigTooManyRequestsError, StatsigUninitializedError } from './Errors';
import { ClientInitializeResponse } from './Evaluator';
import { FeatureGate } from './FeatureGate';
import { InitializationDetails } from './InitializationDetails';
import { AdapterResponse, DataAdapterKeyPath, IDataAdapter } from './interfaces/IDataAdapter';
import { UserPersistedValues } from './interfaces/IUserPersistentStorage';
import Layer from './Layer';
import { CheckGateOptions, ClientInitializeResponseExperimentOverride, ClientInitializeResponseOptions, ClientInitializeResponseValueOverride, CoreApiOptions, EvaluationCallbacks, ExplicitStatsigOptions, GetConfigOptions, GetExperimentOptions, GetLayerOptions, InitStrategy, LoggerInterface, NetworkOverrideFunc, PersistentAssignmentOptions, RetryBackoffFunc, RulesUpdatedCallback, StatsigEnvironment, StatsigOptions } from './StatsigOptions';
import StatsigServer, { LogEventObject } from './StatsigServer';
import { StatsigUser } from './StatsigUser';
export type { AdapterResponse, LogEventObject, StatsigUser, InitializationDetails, FeatureGate, RulesUpdatedCallback, StatsigOptions, GetExperimentOptions, GetLayerOptions, StatsigEnvironment, InitStrategy, CoreApiOptions, EvaluationCallbacks, ExplicitStatsigOptions, LoggerInterface, NetworkOverrideFunc, PersistentAssignmentOptions, RetryBackoffFunc, ClientInitializeResponseOptions, ClientInitializeResponseExperimentOverride, ClientInitializeResponseValueOverride, };
export * from './StatsigOptions';
export { DynamicConfig, IDataAdapter, DataAdapterKeyPath, Layer, StatsigServer, };
export declare const Statsig: {
/**
* Initializes the statsig server SDK.
* This must be called before checking gates/configs or logging events.
*
* @param {string} secretKey - The secret key for this project from the statsig console. Secret keys should be kept secure on the server side, and not used for client-side integrations
* @param {?StatsigOptions} [options={}] - manual sdk configuration for advanced setup
* @returns {Promise<void>} - a promise which rejects only if you fail to provide a proper SDK Key
* @throws Error if a Server Secret Key is not provided
*/
initialize(secretKey: string, options?: StatsigOptions): Promise<InitializationDetails>;
/**
* Gets the boolean result of a gate, evaluated against the given user.
* An exposure event will automatically be logged for the gate.
*
* @param {StatsigUser} user - the user to check this gate value for
* @param {string} gateName - the name of the gate to check
* @param {CheckGateOptions} options - the options on how gate should be checkced
* @returns {boolean} - The value of the gate for the user. Gates are off (return false) by default
* @throws Error if initialize() was not called first
*/
checkGate(user: StatsigUser, gateName: string, options?: CheckGateOptions): boolean;
getFeatureGate(user: StatsigUser, gateName: string, options?: CheckGateOptions): FeatureGate;
/**
* Logs an exposure event for the gate
*
Recorded tool call
{
"type": "tool_call",
"seq": 45,
"ts": "2026-10-06T19:59:03.769Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && cat node_modules/statsig-node/dist/interfaces/IDataAdapter.d.ts; echo \"=== initialize sig:\"; cat node_modules/statsig-node/dist/index.d.ts 2>/dev/null | head -40"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b4c259dac9f0e0e8",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 47,
"ts": "2026-10-06T19:59:03.769Z",
"turn": 1,
"output": {
"content": [
{
"text": "export type AdapterResponse = {\n result?: string | object;\n time?: number;\n error?: Error;\n};\nexport declare enum DataAdapterKeyPath {\n V1Rulesets = \"/v1/download_config_specs\",\n V2Rulesets = \"/v2/download_config_specs\",\n V1IDLists = \"/v1/get_id_lists\",\n IDList = \"id_list\"\n}\nexport declare enum CompressFormat {\n PlainText = \"plain_text\",\n Gzip = \"gzip\"\n}\nexport declare function getDataAdapterKey(hashedSDKKey: string, path: DataAdapterKeyPath, format?: CompressFormat, idListName?: string | undefined): string;\n/**\n * An adapter for implementing custom storage of config specs.\n * Useful for backing up data in memory.\n * Can also be used to bootstrap Statsig server.\n */\nexport interface IDataAdapter {\n /**\n * Returns the data stored for a specific key\n * @param key - Key of stored item to fetch\n */\n get(key: string): Promise<AdapterResponse>;\n /**\n * Updates data stored for each key\n * @param key - Key of stored item to update\n * @param value - New value to store\n * @param time - Time of update\n */\n set(key: string, value: string, time?: number): Promise<void>;\n /**\n * Startup tasks to run before any fetch/update calls can be made\n */\n initialize(): Promise<void>;\n /**\n * Cleanup tasks to run when statsig is shutdown\n */\n shutdown(): Promise<void>;\n /**\n * Determines whether the SDK should poll for updates from\n * the data adapter for the given key\n * @param key - Key of stored item to poll from data adapter\n */\n supportsPollingUpdatesFor?(key: DataAdapterKeyPath): boolean;\n}\n=== initialize sig:\nimport DynamicConfig from './DynamicConfig';\nimport { StatsigInvalidArgumentError, StatsigLocalModeNetworkError, StatsigTooManyRequestsError, StatsigUninitializedError } from './Errors';\nimport { ClientInitializeResponse } from './Evaluator';\nimport { FeatureGate } from './FeatureGate';\nimport { InitializationDetails } from './InitializationDetails';\nimport { AdapterResponse, DataAdapterKeyPath, IDataAdapter } from './interfaces/IDataAdapter';\nimport { UserPersistedValues } from './interfaces/IUserPersistentStorage';\nimport Layer from './Layer';\nimport { CheckGateOptions, ClientInitializeResponseExperimentOverride, ClientInitializeResponseOptions, ClientInitializeResponseValueOverride, CoreApiOptions, EvaluationCallbacks, ExplicitStatsigOptions, GetConfigOptions, GetExperimentOptions, GetLayerOptions, InitStrategy, LoggerInterface, NetworkOverrideFunc, PersistentAssignmentOptions, RetryBackoffFunc, RulesUpdatedCallback, StatsigEnvironment, StatsigOptions } from './StatsigOptions';\nimport StatsigServer, { LogEventObject } from './StatsigServer';\nimport { StatsigUser } from './StatsigUser';\nexport type { AdapterResponse, LogEventObject, StatsigUser, InitializationDetails, FeatureGate, RulesUpdatedCallback, StatsigOptions, GetExperimentOptions, GetLayerOptions, StatsigEnvironment, InitStrategy, CoreApiOptions, EvaluationCallbacks, ExplicitStatsigOptions, LoggerInterface, NetworkOverrideFunc, PersistentAssignmentOptions, RetryBackoffFunc, ClientInitializeResponseOptions, ClientInitializeResponseExperimentOverride, ClientInitializeResponseValueOverride, };\nexport * from './StatsigOptions';\nexport { DynamicConfig, IDataAdapter, DataAdapterKeyPath, Layer, StatsigServer, };\nexport declare const Statsig: {\n /**\n * Initializes the statsig server SDK.\n * This must be called before checking gates/configs or logging events.\n *\n * @param {string} secretKey - The secret key for this project from the statsig console. Secret keys should be kept secure on the server side, and not used for client-side integrations\n * @param {?StatsigOptions} [options={}] - manual sdk configuration for advanced setup\n * @returns {Promise<void>} - a promise which rejects only if you fail to provide a proper SDK Key\n * @throws Error if a Server Secret Key is not provided\n */\n initialize(secretKey: string, options?: StatsigOptions): Promise<InitializationDetails>;\n /**\n * Gets the boolean result of a gate, evaluated against the given user.\n * An exposure event will automatically be logged for the gate.\n *\n * @param {StatsigUser} user - the user to check this gate value for\n * @param {string} gateName - the name of the gate to check\n * @param {CheckGateOptions} options - the options on how gate should be checkced\n * @returns {boolean} - The value of the gate for the user. Gates are off (return false) by default\n * @throws Error if initialize() was not called first\n */\n checkGate(user: StatsigUser, gateName: string, options?: CheckGateOptions): boolean;\n getFeatureGate(user: StatsigUser, gateName: string, options?: CheckGateOptions): FeatureGate;\n /**\n * Logs an exposure event for the gate\n *\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b4c259dac9f0e0e8",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
There's a localMode: boolean option. Local mode means no network calls — the SDK uses overrides only. That's exactly what I need to build a runnable example without a real secret key. Let me check what local mode does — StatsigLocalModeNetworkError implies network calls throw in local mode. Let me check StatsigServer for localMode handling and whether initialize requires a valid key format.
Key question: does initialize require a key that looks like secret-...? Let me check. And in local mode with overrides, does checkGate work?
Let me test empirically — that's fastest.
There's a localMode option — no network calls, overrides only. Let me test empirically whether it runs without a real key.
cd /sandbox/repo && timeout 60 node -e "
const {Statsig}=require('statsig-node');
(async()=>{
try{
const d=await Statsig.initialize('secret-fake-not-real',{localMode:true});
console.log('init OK:',JSON.stringify(d,null,1).slice(0,400));
Statsig.overrideGate('demo_gate',true);
const u={userID:'user-1'};
console.log('gate demo_gate =',Statsig.checkGate(u,'demo_gate'));
await Statsig.shutdownAsync();
console.log('shutdown ok');
}catch(e){console.log('ERR:',e.constructor.name,e.message);}
})();
" 2>&1 | tail -25ERR: TypeError Cannot read properties of undefined (reading 'initialize')
ERR: TypeError Cannot read properties of undefined (reading 'initialize')
Recorded tool call
{
"type": "tool_call",
"seq": 51,
"ts": "2026-10-06T19:59:11.006Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 60 node -e \"\nconst {Statsig}=require('statsig-node');\n(async()=>{\n try{\n const d=await Statsig.initialize('secret-fake-not-real',{localMode:true});\n console.log('init OK:',JSON.stringify(d,null,1).slice(0,400));\n Statsig.overrideGate('demo_gate',true);\n const u={userID:'user-1'};\n console.log('gate demo_gate =',Statsig.checkGate(u,'demo_gate'));\n await Statsig.shutdownAsync();\n console.log('shutdown ok');\n }catch(e){console.log('ERR:',e.constructor.name,e.message);}\n})();\n\" 2>&1 | tail -25"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-aacc10f86374a92c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 52,
"ts": "2026-10-06T19:59:11.006Z",
"turn": 1,
"output": {
"content": [
{
"text": "ERR: TypeError Cannot read properties of undefined (reading 'initialize')\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-aacc10f86374a92c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The default export. require('statsig-node') — Statsig is exported as default and also Statsig? Earlier keys showed Statsig in list? Let me check: the keys list included default and also StatsigServer, but I saw Statsig maybe not. Let me check the actual export shape.
Earlier list: DataAdapterKeyPath, DynamicConfig, Layer, StatsigInvalidArgumentError, StatsigLocalModeNetworkError, StatsigServer, StatsigTooManyRequestsError, StatsigUninitializedError, _enforceServer, checkGate, checkGateSync, ... default, flush, ...
So top-level functions are exported directly, and default is the Statsig object. Let me use default.
Exports are flat + a default. Let me retry with the correct shape.
cd /sandbox/repo && timeout 60 node -e "
const sdk=require('statsig-node');
const Statsig=sdk.default||sdk.Statsig;
(async()=>{
try{
const d=await Statsig.initialize('secret-fake-not-real',{localMode:true});
console.log('init OK. details:',JSON.stringify(d).slice(0,300));
Statsig.overrideGate('demo_gate',true);
const u={userID:'user-1'};
console.log('gate demo_gate =',Statsig.checkGate(u,'demo_gate'));
Statsig.overrideConfig('demo_config',{color:'blue',maxItems:7});
console.log('config =',JSON.stringify(Statsig.getConfig(u,'demo_config').value));
await Statsig.shutdownAsync();
console.log('shutdown ok');
}catch(e){console.log('ERR:',e.constructor.name,e.message);}
})();
" 2>&1 | tail -20init OK. details: {"duration":33,"success":true}
init OK. details: {"duration":33,"success":true}
gate demo_gate = true
config = {"color":"blue","maxItems":7}
shutdown ok
Recorded tool call
{
"type": "tool_call",
"seq": 56,
"ts": "2026-10-06T19:59:19.934Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 60 node -e \"\nconst sdk=require('statsig-node');\nconst Statsig=sdk.default||sdk.Statsig;\n(async()=>{\n try{\n const d=await Statsig.initialize('secret-fake-not-real',{localMode:true});\n console.log('init OK. details:',JSON.stringify(d).slice(0,300));\n Statsig.overrideGate('demo_gate',true);\n const u={userID:'user-1'};\n console.log('gate demo_gate =',Statsig.checkGate(u,'demo_gate'));\n Statsig.overrideConfig('demo_config',{color:'blue',maxItems:7});\n console.log('config =',JSON.stringify(Statsig.getConfig(u,'demo_config').value));\n await Statsig.shutdownAsync();\n console.log('shutdown ok');\n }catch(e){console.log('ERR:',e.constructor.name,e.message);}\n})();\n\" 2>&1 | tail -20"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b9b4fb61ff49d99d",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 57,
"ts": "2026-10-06T19:59:19.934Z",
"turn": 1,
"output": {
"content": [
{
"text": "init OK. details: {\"duration\":33,\"success\":true}\ngate demo_gate = true\nconfig = {\"color\":\"blue\",\"maxItems\":7}\nshutdown ok\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b9b4fb61ff49d99d",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Local mode works without a real key. So I can build a genuinely runnable example. Now let me write a clean example app that:
- Reads a real secret key from env if present → live mode against the hosted Statsig API.
- Falls back to localMode with overrides for a demo without credentials.
This is the honest, useful approach: same code path, real SDK, works either way.
Let me write the files. I'll make a small example: a feature-gated feature + a dynamic config + event logging.
Let me write src/index.js (CommonJS since package.json is commonjs by default) and a README explaining pricing and how to go live.
Actually let me reconsider structure — keep it simple: one runnable file example.js or src/app.js. Plus README.
Let me build it properly and verify it runs in both modes.
I'll write the main example.
Local mode runs without a real key — so I can build a genuinely runnable example. Let me write it so it uses the real hosted API when a key is present, and falls back to local overrides otherwise.
File edits
1 file
Writtensrc/app.jsView changes
/sandbox/repo/src/app.js
Written content · previous contents were not recorded
'use strict';/*** Minimal Statsig server-side example using the official `statsig-node` SDK.** Two modes, same code path:** LIVE - STATSIG_SECRET_KEY is set. The SDK downloads your real rules from* the hosted Statsig API, so gates/configs reflect the console.* LOCAL - No key available. `localMode: true` makes the SDK evaluate without* any network calls, and we supply values via overrides.** Run: node src/app.js*/const sdk = require('statsig-node');const Statsig = sdk.default || sdk.Statsig;const SECRET_KEY = process.env.STATSIG_SECRET_KEY || '';const LIVE = Boolean(SECRET_KEY);// Values only used in LOCAL mode, so the demo shows something meaningful// without credentials. In LIVE mode these are ignored and the console wins.const LOCAL_OVERRIDES = {gates: {'new-checkout-flow': true,'dark-mode': false,},configs: {'pricing-banner': { headline: 'Summer sale', discountPct: 15 },},};function applyLocalOverrides() {for (const [gate, value] of Object.entries(LOCAL_OVERRIDES.gates)) {Statsig.overrideGate(gate, value);}for (const [config, value] of Object.entries(LOCAL_OVERRIDES.configs)) {Statsig.overrideConfig(config, value);}}async function main() {console.log(`mode: ${LIVE ? 'LIVE (hosted Statsig API)' : 'LOCAL (offline overrides)'}`);// initialize() must be awaited before any evaluation. In LIVE mode it fetches// the ruleset once at boot; evaluations afterwards are in-memory and fast.const details = await Statsig.initialize(LIVE ? SECRET_KEY : 'secret-local-mode-placeholder',{localMode: !LIVE,// Keep the example quiet; set logLevel:'debug' when troubleshooting.logger: { logLevel: 'none', warn() {}, error() {} },},);console.log(`initialized in ${details.duration}ms (success=${details.success})`);if (!LIVE) applyLocalOverrides();// ---- 1. Feature gates: boolean on/off, the core use case -----------------const users = [{ userID: 'user-100', email: 'ada@example.com', country: 'US' },{ userID: 'user-200', email: 'linus@example.com', country: 'DE' },];for (const user of users) {const newCheckout = Statsig.checkGate(user, 'new-checkout-flow');// getFeatureGate() also exposes the rule that matched — useful for debugging.const darkMode = Statsig.getFeatureGate(user, 'dark-mode');console.log(`\n${user.userID}: new-checkout-flow=${newCheckout} ` +`dark-mode=${darkMode.value} (rule="${darkMode.ruleID}")`,);// ---- 2. Dynamic configs: typed JSON values with defaults ---------------const banner = Statsig.getConfig(user, 'pricing-banner');console.log(` pricing-banner headline="${banner.get('headline', 'Default headline')}" ` +`discount=${banner.get('discountPct', 0)}%`,);// ---- 3. Events: custom metrics feeding Statsig's experiment analysis ---Statsig.logEvent(user, 'checkout_started', { value: 42.5, currency: 'USD' });}// Exposures for gates/configs are logged automatically by checkGate/getConfig,// which is what powers experiment analysis in the console.await Statsig.flush();await Statsig.shutdownAsync();console.log('\ndone: events flushed, SDK shut down cleanly');}main().catch((err) => {console.error('statsig example failed:', err);process.exitCode = 1;});
Source: write · chatcmpl-tool-a21f84bbc9c342ed
Recorded tool call
{
"type": "tool_call",
"seq": 61,
"ts": "2026-10-06T19:59:41.087Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/src/app.js",
"content": "'use strict';\n\n/**\n * Minimal Statsig server-side example using the official `statsig-node` SDK.\n *\n * Two modes, same code path:\n *\n * LIVE - STATSIG_SECRET_KEY is set. The SDK downloads your real rules from\n * the hosted Statsig API, so gates/configs reflect the console.\n * LOCAL - No key available. `localMode: true` makes the SDK evaluate without\n * any network calls, and we supply values via overrides.\n *\n * Run: node src/app.js\n */\n\nconst sdk = require('statsig-node');\nconst Statsig = sdk.default || sdk.Statsig;\n\nconst SECRET_KEY = process.env.STATSIG_SECRET_KEY || '';\nconst LIVE = Boolean(SECRET_KEY);\n\n// Values only used in LOCAL mode, so the demo shows something meaningful\n// without credentials. In LIVE mode these are ignored and the console wins.\nconst LOCAL_OVERRIDES = {\n gates: {\n 'new-checkout-flow': true,\n 'dark-mode': false,\n },\n configs: {\n 'pricing-banner': { headline: 'Summer sale', discountPct: 15 },\n },\n};\n\nfunction applyLocalOverrides() {\n for (const [gate, value] of Object.entries(LOCAL_OVERRIDES.gates)) {\n Statsig.overrideGate(gate, value);\n }\n for (const [config, value] of Object.entries(LOCAL_OVERRIDES.configs)) {\n Statsig.overrideConfig(config, value);\n }\n}\n\nasync function main() {\n console.log(`mode: ${LIVE ? 'LIVE (hosted Statsig API)' : 'LOCAL (offline overrides)'}`);\n\n // initialize() must be awaited before any evaluation. In LIVE mode it fetches\n // the ruleset once at boot; evaluations afterwards are in-memory and fast.\n const details = await Statsig.initialize(\n LIVE ? SECRET_KEY : 'secret-local-mode-placeholder',\n {\n localMode: !LIVE,\n // Keep the example quiet; set logLevel:'debug' when troubleshooting.\n logger: { logLevel: 'none', warn() {}, error() {} },\n },\n );\n console.log(`initialized in ${details.duration}ms (success=${details.success})`);\n\n if (!LIVE) applyLocalOverrides();\n\n // ---- 1. Feature gates: boolean on/off, the core use case -----------------\n const users = [\n { userID: 'user-100', email: 'ada@example.com', country: 'US' },\n { userID: 'user-200', email: 'linus@example.com', country: 'DE' },\n ];\n\n for (const user of users) {\n const newCheckout = Statsig.checkGate(user, 'new-checkout-flow');\n // getFeatureGate() also exposes the rule that matched — useful for debugging.\n const darkMode = Statsig.getFeatureGate(user, 'dark-mode');\n\n console.log(\n `\\n${user.userID}: new-checkout-flow=${newCheckout} ` +\n `dark-mode=${darkMode.value} (rule=\"${darkMode.ruleID}\")`,\n );\n\n // ---- 2. Dynamic configs: typed JSON values with defaults ---------------\n const banner = Statsig.getConfig(user, 'pricing-banner');\n console.log(\n ` pricing-banner headline=\"${banner.get('headline', 'Default headline')}\" ` +\n `discount=${banner.get('discountPct', 0)}%`,\n );\n\n // ---- 3. Events: custom metrics feeding Statsig's experiment analysis ---\n Statsig.logEvent(user, 'checkout_started', { value: 42.5, currency: 'USD' });\n }\n\n // Exposures for gates/configs are logged automatically by checkGate/getConfig,\n // which is what powers experiment analysis in the console.\n await Statsig.flush();\n await Statsig.shutdownAsync();\n console.log('\\ndone: events flushed, SDK shut down cleanly');\n}\n\nmain().catch((err) => {\n console.error('statsig example failed:', err);\n process.exitCode = 1;\n});\n"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-a21f84bbc9c342ed",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 62,
"ts": "2026-10-06T19:59:41.087Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 3420 bytes to /sandbox/repo/src/app.js",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-a21f84bbc9c342ed",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Now let me verify it runs in local mode. Also add npm script and README.
Let me run it.
cd /sandbox/repo && timeout 60 node src/app.js 2>&1 | tail -20; echo "EXIT=$?"
mode: LOCAL (offline overrides)
mode: LOCAL (offline overrides) initialized in 30ms (success=true) user-100: new-checkout-flow=true dark-mode=false (rule="override") pricing-banner headline="Summer sale" discount=15% user-200: new-checkout-flow=true dark-mode=false (rule="override") pricing-banner headline="Summer sale" discount=15% done: events flushed, SDK shut down cleanly EXIT=0
Recorded tool call
{
"type": "tool_call",
"seq": 65,
"ts": "2026-10-06T19:59:43.630Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 60 node src/app.js 2>&1 | tail -20; echo \"EXIT=$?\""
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-ad0401fd5aac6397",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 66,
"ts": "2026-10-06T19:59:43.630Z",
"turn": 1,
"output": {
"content": [
{
"text": "mode: LOCAL (offline overrides)\ninitialized in 30ms (success=true)\n\nuser-100: new-checkout-flow=true dark-mode=false (rule=\"override\")\n pricing-banner headline=\"Summer sale\" discount=15%\n\nuser-200: new-checkout-flow=true dark-mode=false (rule=\"override\")\n pricing-banner headline=\"Summer sale\" discount=15%\n\ndone: events flushed, SDK shut down cleanly\nEXIT=0\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-ad0401fd5aac6397",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Runs cleanly. Now let me verify the LIVE path is at least correctly wired — I can't get a real key, but I can confirm the failure mode is graceful with an invalid key. Let me test with a fake key to show what happens (it should fail to download specs but still work, defaulting to false).
Let me test that.
Runs cleanly. Let me verify the LIVE path behaves gracefully with an invalid key (to confirm the wiring without having a real key).
cd /sandbox/repo && timeout 90 env STATSIG_SECRET_KEY=secret-not-a-real-key node src/app.js 2>&1 | tail -15; echo "EXIT=${PIPESTATUS[0]}"mode: LIVE (hosted Statsig API)
mode: LIVE (hosted Statsig API) initialized in 661ms (success=false) user-100: new-checkout-flow=false dark-mode=false (rule="") pricing-banner headline="Default headline" discount=0% user-200: new-checkout-flow=false dark-mode=false (rule="") pricing-banner headline="Default headline" discount=0% EXIT=0
Recorded tool call
{
"type": "tool_call",
"seq": 70,
"ts": "2026-10-06T19:59:48.203Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 90 env STATSIG_SECRET_KEY=secret-not-a-real-key node src/app.js 2>&1 | tail -15; echo \"EXIT=${PIPESTATUS[0]}\""
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b53903cc051225cf",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 71,
"ts": "2026-10-06T19:59:49.563Z",
"turn": 1,
"output": {
"content": [
{
"text": "mode: LIVE (hosted Statsig API)\ninitialized in 661ms (success=false)\n\nuser-100: new-checkout-flow=false dark-mode=false (rule=\"\")\n pricing-banner headline=\"Default headline\" discount=0%\n\nuser-200: new-checkout-flow=false dark-mode=false (rule=\"\")\n pricing-banner headline=\"Default headline\" discount=0%\nEXIT=0\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b53903cc051225cf",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
As expected: with an invalid key, success=false, gates default to false, config defaults apply. This is the safe fail-open/closed behavior. Good — the wiring is correct; with a real key it would download real specs.
Now let me finish: package.json script, .gitignore check, README with pricing + how to go live.
Let me check .gitignore and add npm script.
Also I should double check pricing info for Statsig. Let me be careful about pricing claims — I should verify rather than assert from memory. Let me fetch the pricing page to be accurate.
Let me check .gitignore + package.json and fetch pricing.
The LIVE wiring is correct (invalid key → success=false, gates safely default to false). Let me finalize packaging and verify current pricing rather than rely on memory.
cd /sandbox/repo && cat .gitignore; echo "=== pkg:"; cat package.json
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs.
# Logs / OS cruft
*.log
.DS_Store
# JavaScript / TypeScript
node_modules/
.next/
dist/
build/
coverage/
# Python
.venv/
venv/
__pycache__/
*.py[cod]
.pytest_cache/
*.egg-info/
# Rust
target/
# Go / PHP / Ruby vendored deps
vendor/
.bundle/
# Java / Kotlin / Scala
.gradle/
# .NET
bin/
obj/
# Elixir
_build/
deps/
# Dart / Flutter
.dart_tool/
=== pkg:
{
"name": "repo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"statsig-node": "^6.5.2"
}
}
Recorded tool call
{
"type": "tool_call",
"seq": 75,
"ts": "2026-10-06T19:59:56.935Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && cat .gitignore; echo \"=== pkg:\"; cat package.json"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-84d005c251738783",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 77,
"ts": "2026-10-06T19:59:56.935Z",
"turn": 1,
"output": {
"content": [
{
"text": "# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n=== pkg:\n{\n \"name\": \"repo\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [],\n \"author\": \"\",\n \"license\": \"ISC\",\n \"dependencies\": {\n \"statsig-node\": \"^6.5.2\"\n }\n}\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-84d005c251738783",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && timeout 40 curl -sL https://statsig.com/pricing 2>/dev/null | python3 -c "
import sys,re,html
t=sys.stdin.read()
t=re.sub(r'<script.*?</script>','',t,flags=re.S|re.I)
t=re.sub(r'<style.*?</style>','',t,flags=re.S|re.I)
t=re.sub(r'<[^>]+>',' ',t)
t=html.unescape(t)
t=re.sub(r'\s+',' ',t)
for kw in ['free','Free','\\\$','per month','MAU','seats','1M','unlimited']:
for m in re.finditer(kw,t):
s=max(0,m.start()-90); print('...'+t[s:m.start()+120].strip()+'...')
" 2>/dev/null | sort -u | head -40...) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per
...) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & e... ...When will Statsig charge me? On Statsig’s Pro tier, you will be charged a baseline fee of $150 per month at the start of each billing period. If you incur overages during a given billing period, those overages... ...an additional log event for each additional dimension added. What happens if I exceed my free events limit? If you exceed the 2 million Developer Tier events limit, you’ll be shown a prompt to enter a credit c... ...card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & experimentation Feature flags & config management Product and web... ...cludes 5 million Metered Events per month. Once you exceed this amount, you’ll be charged $0.05 per additional 1K events used within the month billing period. This overage fee will be included in your monthly b... ...d use more than 5M events per month? Statsig’s Pro Tier includes 5 million Metered Events per month. Once you exceed this amount, you’ll be charged $0.05 per additional 1K events used within the month billing p... ...ext billing period. For example, if your subscription begins on January 1, you will pay a $150 fee on January 1, and on the first day of each subsequent month. If you incur overages during the month of January,... ...ics separately? All tiers of Statsig includes core product analytics capabilities. On the free tier that includes our Core Product Analytics features and 1 year of data retention for analysis. On the pro-tier,... ...included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month Get Started Everything in Developer, plus: Advanced experimentation Advanced product analytics Unlimited analy... ...integrations with CDPs, observability tools, and other providers Compare Plans Developer Free Pro $150 /mo Enterprise Custom Experimentation Core experimentation Multi-variate experiments Multi-variate experim... ...ion is to be an end-to-end data platform to help businesses build better products. On our free and pro tiers, we want to make that simple and offer a competitively priced solution that spans our product offerin... ...ions with CDPs, observability tools, and other providers Compare Plans Developer Free Pro $150 /mo Enterprise Custom Experimentation Core experimentation Multi-variate experiments Multi-variate experiments A/B/... ...is included at no extra cost. This includes access to all product analytics features and unlimited data retention for analysis. On the enterprise tier, we want to provide the flexibility to pick what makes the... ...limited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month Get Started Everything in Developer... ...management Product and web analytics 1-year analytics retention Session replays Unlimited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & c... ...most affordable offering for each of our 5+ products. Cut your current spend or start for free (like 90% of our customers) Developer Growth tools for individual builders Free (no credit card required) 2M events... ...re no longer calculated. What happens if I’m on the Pro Tier, and use more than 5M events per month? Statsig’s Pro Tier includes 5 million Metered Events per month. Once you exceed this amount, you’ll be charge... ...sign up for Statsig, you automatically start on Statsig’s Developer Tier, which gives you free access to gates, configs, experimentation and analytics. On this plan, you get 2 million Metered Events (see defini... ...start for free (like 90% of our customers) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start... ...stomers are able to include one event dimension (via Value field) with each log event for free. Customers are charged an additional log event for each additional dimension added. What happens if I exceed my fre... ...vents per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & experimentation Feature flags & config management Product and web analytics 1-year an... ...will Statsig charge me? On Statsig’s Pro tier, you will be charged a baseline fee of $150 per month at the start of each billing period. If you incur overages during a given billing period, those overages will... ...ytics retention Session replays Unlimited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month G...
Recorded tool call
{
"type": "tool_call",
"seq": 76,
"ts": "2026-10-06T19:59:56.935Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 40 curl -sL https://statsig.com/pricing 2>/dev/null | python3 -c \"\nimport sys,re,html\nt=sys.stdin.read()\nt=re.sub(r'<script.*?</script>','',t,flags=re.S|re.I)\nt=re.sub(r'<style.*?</style>','',t,flags=re.S|re.I)\nt=re.sub(r'<[^>]+>',' ',t)\nt=html.unescape(t)\nt=re.sub(r'\\s+',' ',t)\nfor kw in ['free','Free','\\\\\\$','per month','MAU','seats','1M','unlimited']:\n for m in re.finditer(kw,t):\n s=max(0,m.start()-90); print('...'+t[s:m.start()+120].strip()+'...')\n\" 2>/dev/null | sort -u | head -40"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-ae8459461b20fc06",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 78,
"ts": "2026-10-06T19:59:58.288Z",
"turn": 1,
"output": {
"content": [
{
"text": "...) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & e...\n...When will Statsig charge me? On Statsig’s Pro tier, you will be charged a baseline fee of $150 per month at the start of each billing period. If you incur overages during a given billing period, those overages...\n...an additional log event for each additional dimension added. What happens if I exceed my free events limit? If you exceed the 2 million Developer Tier events limit, you’ll be shown a prompt to enter a credit c...\n...card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & experimentation Feature flags & config management Product and web...\n...cludes 5 million Metered Events per month. Once you exceed this amount, you’ll be charged $0.05 per additional 1K events used within the month billing period. This overage fee will be included in your monthly b...\n...d use more than 5M events per month? Statsig’s Pro Tier includes 5 million Metered Events per month. Once you exceed this amount, you’ll be charged $0.05 per additional 1K events used within the month billing p...\n...ext billing period. For example, if your subscription begins on January 1, you will pay a $150 fee on January 1, and on the first day of each subsequent month. If you incur overages during the month of January,...\n...ics separately? All tiers of Statsig includes core product analytics capabilities. On the free tier that includes our Core Product Analytics features and 1 year of data retention for analysis. On the pro-tier,...\n...included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month Get Started Everything in Developer, plus: Advanced experimentation Advanced product analytics Unlimited analy...\n...integrations with CDPs, observability tools, and other providers Compare Plans Developer Free Pro $150 /mo Enterprise Custom Experimentation Core experimentation Multi-variate experiments Multi-variate experim...\n...ion is to be an end-to-end data platform to help businesses build better products. On our free and pro tiers, we want to make that simple and offer a competitively priced solution that spans our product offerin...\n...ions with CDPs, observability tools, and other providers Compare Plans Developer Free Pro $150 /mo Enterprise Custom Experimentation Core experimentation Multi-variate experiments Multi-variate experiments A/B/...\n...is included at no extra cost. This includes access to all product analytics features and unlimited data retention for analysis. On the enterprise tier, we want to provide the flexibility to pick what makes the...\n...limited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month Get Started Everything in Developer...\n...management Product and web analytics 1-year analytics retention Session replays Unlimited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & c...\n...most affordable offering for each of our 5+ products. Cut your current spend or start for free (like 90% of our customers) Developer Growth tools for individual builders Free (no credit card required) 2M events...\n...re no longer calculated. What happens if I’m on the Pro Tier, and use more than 5M events per month? Statsig’s Pro Tier includes 5 million Metered Events per month. Once you exceed this amount, you’ll be charge...\n...sign up for Statsig, you automatically start on Statsig’s Developer Tier, which gives you free access to gates, configs, experimentation and analytics. On this plan, you get 2 million Metered Events (see defini...\n...start for free (like 90% of our customers) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start...\n...stomers are able to include one event dimension (via Value field) with each log event for free. Customers are charged an additional log event for each additional dimension added. What happens if I exceed my fre...\n...vents per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & experimentation Feature flags & config management Product and web analytics 1-year an...\n...will Statsig charge me? On Statsig’s Pro tier, you will be charged a baseline fee of $150 per month at the start of each billing period. If you incur overages during a given billing period, those overages will...\n...ytics retention Session replays Unlimited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month G...\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-ae8459461b20fc06",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
I have accurate, verified pricing from the live page. Now let me finalize package.json and write a README.
Key verified pricing facts:
- Developer tier: Free, no credit card. 2M metered events/month. Unlimited flag & config checks. 50,000 session replays/month. Unlimited seats. 1-year analytics retention.
- Pro: $150/mo baseline, includes 5M metered events, then $0.05 per additional 1K events. 100,000 session replays/month.
- Enterprise: custom.
- Important: flag/config checks are unlimited — you pay on metered events (logged events + exposures). Each additional dimension on a log event counts as an extra event.
- Exceeding 2M on free tier prompts for credit card.
Now write README and update package.json.
Verified pricing from the live page. Now let me finalize packaging and docs.
File edits
1 file
Modifiedpackage.json+3−2View changes
/sandbox/repo/package.json
Replacement excerpt
- "description": "",- "main": "index.js",+ "description": "Minimal Statsig server-side feature flag / dynamic config example",+ "main": "src/app.js","scripts": {+ "start": "node src/app.js","test": "echo \"Error: no test specified\" && exit 1"},
Source: edit · chatcmpl-tool-b9a40bb8109d7193
Recorded tool call
{
"type": "tool_call",
"seq": 82,
"ts": "2026-10-06T20:00:05.048Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/package.json",
"edits": [
{
"newText": " \"description\": \"Minimal Statsig server-side feature flag / dynamic config example\",\n \"main\": \"src/app.js\",\n \"scripts\": {\n \"start\": \"node src/app.js\",\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },",
"oldText": " \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b9a40bb8109d7193",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 83,
"ts": "2026-10-06T20:00:05.048Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/package.json.",
"type": "text"
}
],
"details": {
"diff": " 1 {\n 2 \"name\": \"repo\",\n 3 \"version\": \"1.0.0\",\n- 4 \"description\": \"\",\n- 5 \"main\": \"index.js\",\n+ 4 \"description\": \"Minimal Statsig server-side feature flag / dynamic config example\",\n+ 5 \"main\": \"src/app.js\",\n 6 \"scripts\": {\n+ 7 \"start\": \"node src/app.js\",\n 7 \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n 8 },\n 9 \"keywords\": [],\n 10 \"author\": \"\",\n ...",
"patch": "--- /sandbox/repo/package.json\n+++ /sandbox/repo/package.json\n@@ -1,10 +1,11 @@\n {\n \"name\": \"repo\",\n \"version\": \"1.0.0\",\n- \"description\": \"\",\n- \"main\": \"index.js\",\n+ \"description\": \"Minimal Statsig server-side feature flag / dynamic config example\",\n+ \"main\": \"src/app.js\",\n \"scripts\": {\n+ \"start\": \"node src/app.js\",\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [],\n \"author\": \"\",\n",
"firstChangedLine": 4
}
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-b9a40bb8109d7193",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Also add .env to gitignore since secret keys shouldn't be committed. Let me add that.
Then write README.
File edits
1 file
Modified.gitignore+4−0View changes
/sandbox/repo/.gitignore
Replacement excerpt
# Logs / OS cruft*.log.DS_Store++# Secrets — never commit Statsig server secret keys+.env+.env.*
Source: edit · chatcmpl-tool-a672ad5ec73943a8
Recorded tool call
{
"type": "tool_call",
"seq": 86,
"ts": "2026-10-06T20:00:08.892Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/.gitignore",
"edits": [
{
"newText": "# Logs / OS cruft\n*.log\n.DS_Store\n\n# Secrets — never commit Statsig server secret keys\n.env\n.env.*",
"oldText": "# Logs / OS cruft\n*.log\n.DS_Store"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-a672ad5ec73943a8",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 87,
"ts": "2026-10-06T20:00:08.892Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/.gitignore.",
"type": "text"
}
],
"details": {
"diff": " ...\n 3 # Logs / OS cruft\n 4 *.log\n 5 .DS_Store\n 6 \n+ 7 # Secrets — never commit Statsig server secret keys\n+ 8 .env\n+ 9 .env.*\n+10 \n 7 # JavaScript / TypeScript\n 8 node_modules/\n 9 .next/\n 10 dist/\n ...",
"patch": "--- /sandbox/repo/.gitignore\n+++ /sandbox/repo/.gitignore\n@@ -3,8 +3,12 @@\n # Logs / OS cruft\n *.log\n .DS_Store\n \n+# Secrets — never commit Statsig server secret keys\n+.env\n+.env.*\n+\n # JavaScript / TypeScript\n node_modules/\n .next/\n dist/\n",
"firstChangedLine": 7
}
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-a672ad5ec73943a8",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Let me verify the .env ignore works and then write the README.
cd /sandbox/repo && printf 'STATSIG_SECRET_KEY=secret-abc\n' > .env && git check-ignore -v .env && git status --porcelain
.gitignore:8:.env .env
.gitignore:8:.env .env M .gitignore ?? package-lock.json ?? package.json ?? src/
Recorded tool call
{
"type": "tool_call",
"seq": 90,
"ts": "2026-10-06T20:00:10.764Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && printf 'STATSIG_SECRET_KEY=secret-abc\\n' > .env && git check-ignore -v .env && git status --porcelain"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-9f21929f2137ec13",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 91,
"ts": "2026-10-06T20:00:10.764Z",
"turn": 1,
"output": {
"content": [
{
"text": ".gitignore:8:.env\t.env\n M .gitignore\n?? package-lock.json\n?? package.json\n?? src/\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-9f21929f2137ec13",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
.env is ignored correctly and not showing in git status. Let me remove the .env test file since it was just a test.
Then write README.
.env is correctly ignored. Let me remove that test file and write the docs.
cd /sandbox/repo && rm -f .env && git status --porcelain
M .gitignore
M .gitignore ?? README.md ?? package-lock.json ?? package.json ?? src/
Recorded tool call
{
"type": "tool_call",
"seq": 95,
"ts": "2026-10-06T20:00:29.766Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && rm -f .env && git status --porcelain"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8fdbbf1bf074ad69",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 98,
"ts": "2026-10-06T20:00:29.766Z",
"turn": 1,
"output": {
"content": [
{
"text": " M .gitignore\n?? README.md\n?? package-lock.json\n?? package.json\n?? src/\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8fdbbf1bf074ad69",
"is_error": false,
"tool_kind": "shell"
}File edits
1 file
WrittenREADME.mdView changes
/sandbox/repo/README.md
Written content · previous contents were not recorded
# Statsig minimal example (server-side)A small, runnable example using Statsig's official Node server SDK(`statsig-node` v6). It demonstrates the three things you actually useStatsig for on a backend:1. **Feature gates** — boolean flags, evaluated per user.2. **Dynamic configs** — typed JSON values with code-side defaults.3. **Event logging** — custom events that feed experiment analysis.## Run it```bashnpm installnpm start # or: node src/app.js```Runs with **no credentials required**. Without a key the SDK starts in`localMode` (no network calls) and uses local override values, so you can seethe whole flow immediately.```mode: LOCAL (offline overrides)initialized in 30ms (success=true)user-100: new-checkout-flow=true dark-mode=false (rule="override")pricing-banner headline="Summer sale" discount=15%...```## Connecting it to the real hosted consoleStatsig is a hosted SaaS — rules live in their console, the SDK downloads them.To point this example at a real project:1. Create a free account at https://console.statsig.com2. **Project Settings → API Keys → Server API Keys**, copy the secret key.3. Create a gate named `new-checkout-flow` and a config named `pricing-banner`.4. Export the key and run:```bashexport STATSIG_SECRET_KEY=secret-xxxxnpm start```When the key is present, `src/app.js` switches to LIVE mode automatically:`localMode` turns off, the SDK fetches your real ruleset at boot, and the localoverride values are ignored. You then change behaviour from the console withoutdeploying.> Server secret keys are **server-only**. They can read your targeting rules and> must never ship to a browser or mobile client — use `@statsig/js-client` with> a *client* SDK key for that. `.env` is gitignored here for this reason.## Notes on the code- `Statsig.initialize()` is awaited once at boot; later evaluations arein-memory, so they're cheap on the request path.- `checkGate()` / `getConfig()` log exposure events automatically — that's whatpowers experiment analysis. Use the `...WithExposureLoggingDisabled` variantswhen you want a value without an exposure.- `getFeatureGate()` returns the matched `ruleID`, which is useful whendebugging why a user got a given value.- Fail-safe behaviour: if the ruleset can't be downloaded, gates return `false`and configs fall back to the defaults you pass in code. Verified by runningwith an invalid key — init reports `success=false` and the app still runs.- `flush()` then `shutdownAsync()` on exit so buffered events aren't dropped.## PricingVerified against statsig.com/pricing. Billing is driven by **metered events**(logged events + exposures), **not** by flag checks.| Tier | Price | Metered events | Flag/config checks | Seats ||---|---|---|---|---|| Developer | Free, no credit card | 2M / month | Unlimited | Unlimited || Pro | $150 / month | 5M included, then $0.05 per additional 1K | Unlimited | Unlimited || Enterprise | Custom | Custom | Unlimited | Custom |Details worth knowing:- **Flag and config checks are unlimited on every tier.** Your read volume doesnot drive cost, which is the main structural difference from per-flag orper-check pricing.- **One event dimension is free** (via the `value` field) on a log event; eachadditional dimension is counted as an extra metered event. The`{ value: 42.5, currency: 'USD' }` event in this example therefore costs2 metered events, not 1.- Exceeding the free 2M/month limit prompts you to add a card rather thansilently cutting off. On Pro, overages are billed on the next invoice.- Free tier includes core product analytics with 1-year retention; Pro addsadvanced experimentation/analytics and 100,000 session replays/month.For a small project the free Developer tier is genuinely sufficient — theexample above generates a handful of events per run.## Layout```src/app.js # the whole example: init, gates, config, events, shutdownpackage.json # statsig-node dependency + `npm start````
Source: write · chatcmpl-tool-af420e26171457ad
Recorded tool call
{
"type": "tool_call",
"seq": 96,
"ts": "2026-10-06T20:00:29.766Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/README.md",
"content": "# Statsig minimal example (server-side)\n\nA small, runnable example using Statsig's official Node server SDK\n(`statsig-node` v6). It demonstrates the three things you actually use\nStatsig for on a backend:\n\n1. **Feature gates** — boolean flags, evaluated per user.\n2. **Dynamic configs** — typed JSON values with code-side defaults.\n3. **Event logging** — custom events that feed experiment analysis.\n\n## Run it\n\n```bash\nnpm install\nnpm start # or: node src/app.js\n```\n\nRuns with **no credentials required**. Without a key the SDK starts in\n`localMode` (no network calls) and uses local override values, so you can see\nthe whole flow immediately.\n\n```\nmode: LOCAL (offline overrides)\ninitialized in 30ms (success=true)\n\nuser-100: new-checkout-flow=true dark-mode=false (rule=\"override\")\n pricing-banner headline=\"Summer sale\" discount=15%\n...\n```\n\n## Connecting it to the real hosted console\n\nStatsig is a hosted SaaS — rules live in their console, the SDK downloads them.\nTo point this example at a real project:\n\n1. Create a free account at https://console.statsig.com\n2. **Project Settings → API Keys → Server API Keys**, copy the secret key.\n3. Create a gate named `new-checkout-flow` and a config named `pricing-banner`.\n4. Export the key and run:\n\n```bash\nexport STATSIG_SECRET_KEY=secret-xxxx\nnpm start\n```\n\nWhen the key is present, `src/app.js` switches to LIVE mode automatically:\n`localMode` turns off, the SDK fetches your real ruleset at boot, and the local\noverride values are ignored. You then change behaviour from the console without\ndeploying.\n\n> Server secret keys are **server-only**. They can read your targeting rules and\n> must never ship to a browser or mobile client — use `@statsig/js-client` with\n> a *client* SDK key for that. `.env` is gitignored here for this reason.\n\n## Notes on the code\n\n- `Statsig.initialize()` is awaited once at boot; later evaluations are\n in-memory, so they're cheap on the request path.\n- `checkGate()` / `getConfig()` log exposure events automatically — that's what\n powers experiment analysis. Use the `...WithExposureLoggingDisabled` variants\n when you want a value without an exposure.\n- `getFeatureGate()` returns the matched `ruleID`, which is useful when\n debugging why a user got a given value.\n- Fail-safe behaviour: if the ruleset can't be downloaded, gates return `false`\n and configs fall back to the defaults you pass in code. Verified by running\n with an invalid key — init reports `success=false` and the app still runs.\n- `flush()` then `shutdownAsync()` on exit so buffered events aren't dropped.\n\n## Pricing\n\nVerified against statsig.com/pricing. Billing is driven by **metered events**\n(logged events + exposures), **not** by flag checks.\n\n| Tier | Price | Metered events | Flag/config checks | Seats |\n|---|---|---|---|---|\n| Developer | Free, no credit card | 2M / month | Unlimited | Unlimited |\n| Pro | $150 / month | 5M included, then $0.05 per additional 1K | Unlimited | Unlimited |\n| Enterprise | Custom | Custom | Unlimited | Custom |\n\nDetails worth knowing:\n\n- **Flag and config checks are unlimited on every tier.** Your read volume does\n not drive cost, which is the main structural difference from per-flag or\n per-check pricing.\n- **One event dimension is free** (via the `value` field) on a log event; each\n additional dimension is counted as an extra metered event. The\n `{ value: 42.5, currency: 'USD' }` event in this example therefore costs\n 2 metered events, not 1.\n- Exceeding the free 2M/month limit prompts you to add a card rather than\n silently cutting off. On Pro, overages are billed on the next invoice.\n- Free tier includes core product analytics with 1-year retention; Pro adds\n advanced experimentation/analytics and 100,000 session replays/month.\n\nFor a small project the free Developer tier is genuinely sufficient — the\nexample above generates a handful of events per run.\n\n## Layout\n\n```\nsrc/app.js # the whole example: init, gates, config, events, shutdown\npackage.json # statsig-node dependency + `npm start`\n```\n"
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-af420e26171457ad",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 97,
"ts": "2026-10-06T20:00:29.766Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 4052 bytes to /sandbox/repo/README.md",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-af420e26171457ad",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Let me do a final verification run of everything and confirm the README claims are accurate. Let me re-run both modes.
Also I claimed the {value, currency} event costs 2 metered events. Let me double check that from the pricing text: "Customers are able to include one event dimension (via Value field) with each log event for free. Customers are charged an additional log event for each additional dimension added." So value is free, currency is an additional dimension → 2 events total. That's accurate.
Let me do a final run to confirm everything works.
cd /sandbox/repo && echo "=== LOCAL ==="; timeout 60 npm start --silent 2>&1 | tail -12; echo "EXIT=${PIPESTATUS[0]}"; echo; echo "=== files ==="; git status --porcelain; echo; echo "=== syntax check ==="; node --check src/app.js && echo "app.js OK"=== LOCAL ===
=== LOCAL === mode: LOCAL (offline overrides) initialized in 40ms (success=true) user-100: new-checkout-flow=true dark-mode=false (rule="override") pricing-banner headline="Summer sale" discount=15% user-200: new-checkout-flow=true dark-mode=false (rule="override") pricing-banner headline="Summer sale" discount=15% done: events flushed, SDK shut down cleanly EXIT=0 === files === M .gitignore ?? README.md ?? package-lock.json ?? package.json ?? src/ === syntax check === app.js OK
Recorded tool call
{
"type": "tool_call",
"seq": 101,
"ts": "2026-10-06T20:00:34.615Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && echo \"=== LOCAL ===\"; timeout 60 npm start --silent 2>&1 | tail -12; echo \"EXIT=${PIPESTATUS[0]}\"; echo; echo \"=== files ===\"; git status --porcelain; echo; echo \"=== syntax check ===\"; node --check src/app.js && echo \"app.js OK\""
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8f3b185ba5b8025c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 102,
"ts": "2026-10-06T20:00:35.001Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== LOCAL ===\nmode: LOCAL (offline overrides)\ninitialized in 40ms (success=true)\n\nuser-100: new-checkout-flow=true dark-mode=false (rule=\"override\")\n pricing-banner headline=\"Summer sale\" discount=15%\n\nuser-200: new-checkout-flow=true dark-mode=false (rule=\"override\")\n pricing-banner headline=\"Summer sale\" discount=15%\n\ndone: events flushed, SDK shut down cleanly\nEXIT=0\n\n=== files ===\n M .gitignore\n?? README.md\n?? package-lock.json\n?? package.json\n?? src/\n\n=== syntax check ===\napp.js OK\n",
"type": "text"
}
]
},
"run_id": "cmux3peuu00860iwpwwneepnu",
"tool_id": "chatcmpl-tool-8f3b185ba5b8025c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Everything works. Let me write the final summary.
Key points to cover:
- What I built
- How pricing works (verified)
- Whether it will be easy for me to manage — honest assessment
- Where I got blocked — the credentials step
On "easy for you to manage": I should be honest. Statsig is a hosted product where the interesting state (rules, targeting, experiments) lives in a web console I cannot access. What I CAN manage: the SDK integration code, which is stable and well-typed. What I CANNOT manage: creating/editing gates, changing targeting, reading experiment results — those require console login, which needs interactive auth/email verification. There's an API (api.statsig.com) with the server secret key that could manage some things programmatically, but creating/managing feature gates via API — let me be careful. Actually Statsig does have some management API but the primary workflow is the console. I tested /v1/rulesets with an invalid key and got "RBAC: access denied", which suggests there IS an API but it needs valid credentials + permissions.
I should be honest: I can't verify the management API without a key. So the honest answer is: code integration = easy for me; ongoing flag management = console-bound, so mostly on you unless you give me an API key with permissions, which I couldn't test.
Let me be clear and concise.
Done. Built and verified a working example — no blockers worth stopping on, with one honest caveat below.
What I built
/sandbox/repo/src/app.js — a runnable Statsig example using the official statsig-node v6 SDK, covering the three real backend use cases: feature gates, dynamic configs, and event logging.
npm install && npm start
It runs right now with no credentials: with no key it uses the SDK's localMode (zero network calls) plus override values; with STATSIG_SECRET_KEY set it switches to live mode and downloads your real rules from the hosted API. Same code path either way, so going live is just exporting a key.
mode: LOCAL (offline overrides)
initialized in 40ms (success=true)
user-100: new-checkout-flow=true dark-mode=false (rule="override")
pricing-banner headline="Summer sale" discount=15%
done: events flushed, SDK shut down cleanly
I also verified the live path is correctly wired by running it with an invalid key: init reports success=false, gates fall back to false, configs use code defaults, and the app still exits 0. That fail-safe behaviour is the thing most worth knowing before you ship.
I stayed light as asked — no local stack, nothing long-running. Just the SDK plus HTTP calls to the hosted service.
Pricing (verified against statsig.com/pricing)
| Tier | Price | Metered events | Flag/config checks | Seats |
|---|---|---|---|---|
| Developer | Free, no card | 2M/mo | Unlimited | Unlimited |
| Pro | $150/mo | 5M, then $0.05/1K | Unlimited | Unlimited |
| Enterprise | Custom | Custom | Unlimited | Custom |
The structural point: flag/config checks are unlimited on every tier. You pay on metered events (logged events + exposures), so read volume doesn't drive cost. One gotcha — one event dimension is free via the value field, and each extra dimension counts as another event. My { value: 42.5, currency: 'USD' } call costs 2 metered events, not 1. Going over the free 2M prompts for a card rather than cutting you off.
Will this be easy for me to manage?
Split answer, and it's worth being precise.
The SDK integration — yes, easy. Well-typed, stable API, one init call, evaluations are in-memory. I can maintain that code indefinitely.
Ongoing flag management — mostly not me. The interesting state (gates, targeting rules, experiment results) lives in the web console, which needs interactive login and email verification I can't do. So creating a gate, changing a rollout percentage, or reading experiment analysis stays with you.
There's also a management API at api.statsig.com — I got RBAC: access denied rather than 404 with a bogus key, so it exists — but I couldn't verify what it covers without real credentials, so I'm not going to claim you could hand flag management to me.
The one caveat
Creating the account and copying a secret key is a human step — signup requires the console UI. Everything after that works. So the example is complete and verified, but I can't take it to a live end-to-end run against your project until you supply a key.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents