All CostHQ errors follow a consistent JSON shape so your agent scripts can handle them programmatically without brittle string matching. Whether a command fails because there is no active session or because a model is unknown, the structure is always the same — only error.code changes.
Error Shape
{
"schemaVersion": 1,
"CostHQVersion": "3.1.1",
"error": {
"code": "no_active_session",
"message": "No active session"
}
}
The schemaVersion and CostHQVersion fields are present on every response, including errors. Parse error.code to branch your logic. Never branch on error.message — it is human-readable prose that may change between versions.
Exit Codes
| Code | Meaning |
|---|
0 | Success |
1 | Error (always, including in --json mode) |
There is no ambiguity: any non-zero exit code means the command failed and an error object is present in the JSON output.
Error Codes
| Code | When it occurs | How to handle |
|---|
session_active | cs start called while a session is already running | Use --close-stale to auto-close the stale session, or --resume to continue it |
no_active_session | cs end or cs log-ai called with no active session | Run cs status --json first to confirm session state |
session_not_found | cs show or cs end -s <id> called with an invalid session ID | Verify the ID with cs list --json |
missing_tokens | cs log-ai called without token counts or a manual cost | Provide --prompt-tokens + --completion-tokens, or -c <cost> |
unknown_model | Model is not in the built-in pricing table and no -c flag was given | Add -c <cost> explicitly, or register custom pricing with cs pricing set |
Parsing Error Codes
import { execSync } from 'child_process';
let output: string;
try {
output = execSync('cs status --json', { encoding: 'utf8' });
} catch (err: any) {
output = err.stdout; // --json errors still write to stdout, exit code 1
}
const result = JSON.parse(output);
if (result.error) {
switch (result.error.code) {
case 'session_active':
// Handle already-running session
break;
case 'no_active_session':
// Start a session first
break;
case 'session_not_found':
// Verify the session ID
break;
}
}
When a command exits with code 1, execSync throws. The error object’s .stdout property still contains the full --json error payload — read it from there, not from stderr.
Failsafe Pattern for Agents
Check that cs is installed before attempting any tracking. If it is absent, skip gracefully rather than blocking your agent’s primary task.
# Check cs is installed before any tracking (Linux / macOS)
which cs >/dev/null 2>&1 || { echo "costhq not installed, skipping tracking"; exit 0; }
# Windows equivalent
where cs >nul 2>&1 || (echo costhq not installed, skipping tracking && exit /b 0)
The same check in Node.js:
import { execSync } from 'child_process';
function isCostHQInstalled(): boolean {
try {
const cmd = process.platform === 'win32' ? 'where cs' : 'which cs';
execSync(cmd, { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
if (!isCostHQInstalled()) {
console.warn('costhq not installed — skipping session tracking');
}
Never string-compare error.message. The human-readable message text may change between CostHQ versions. Always branch on error.code, which is part of the stable contract.