Skip to content

Commit e59942d

Browse files
authored
Clearer error messages (#143)
* Print hypaware not hyp on --version message * Clearer error messages
1 parent 5c47f81 commit e59942d

8 files changed

Lines changed: 68 additions & 12 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ logs / traces / metrics into a local query cache and optional Parquet
88
exports. Everything runs on the local machine — no central server is
99
required for V1.
1010

11+
> Part of **[HypStack](https://hypstack.ai/)**, an open-source stack for AI observability.
12+
1113
## Quickstart
1214

1315
```sh

hypaware-core/plugins-workspace/central/src/identity_client.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ export class IdentityClient {
8888
async bootstrap() {
8989
const token = this.bootstrapToken
9090
if (typeof token !== 'string' || token.length === 0) {
91-
throw new Error('identity bootstrap failed: identity.bootstrap_token is not set')
91+
throw new Error(
92+
'identity bootstrap failed: identity.bootstrap_token is not set. Run `hyp join <central-url> <token>` to enroll this host, or remove the central sink if you only capture locally'
93+
)
9294
}
9395
const url = joinUrl(this.centralUrl, '/v1/identity/bootstrap')
9496
const body = JSON.stringify({ bootstrap_token: token })

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
"smoke": "node ./hypaware-core/smoke/index.js"
3535
},
3636
"dependencies": {
37-
"@aws-sdk/client-s3": "3.1074.0",
37+
"@aws-sdk/client-s3": "3.1075.0",
3838
"@aws-sdk/credential-provider-ini": "3.972.56",
3939
"hyparquet": "1.26.1",
4040
"hyparquet-compressors": "1.1.1",
41-
"icebird": "0.8.10",
41+
"icebird": "0.8.11",
4242
"squirreling": "0.12.24"
4343
},
4444
"optionalDependencies": {
@@ -51,7 +51,7 @@
5151
"squirreling": "0.12.24"
5252
},
5353
"devDependencies": {
54-
"@types/node": "26.0.0",
54+
"@types/node": "26.0.1",
5555
"typescript": "6.0.3"
5656
}
5757
}

src/core/cli/core_commands.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2250,7 +2250,7 @@ async function runVersion(_argv, ctx) {
22502250
const require = createRequire(import.meta.url)
22512251
const { version } = require('../../../package.json')
22522252
const { hypHome } = readObservabilityEnv(ctx.env)
2253-
ctx.stdout.write(`hyp ${version}\n`)
2253+
ctx.stdout.write(`hypaware ${version}\n`)
22542254
ctx.stdout.write(` node: ${process.version}\n`)
22552255
ctx.stdout.write(` platform: ${process.platform} ${process.arch}\n`)
22562256
ctx.stdout.write(` hyp_home: ${hypHome}\n`)

src/core/cli/dispatch.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ import { materializeSinks } from '../sinks/materialize.js'
3232
const HELP_FLAGS = new Set(['--help', '-h', 'help'])
3333
const VERSION_FLAGS = new Set(['--version', '-V'])
3434

35+
/**
36+
* Map a sink materialization failure to a one-line, actionable hint.
37+
* The raw `[errorKind]: message` line stays for operators; this adds a
38+
* "what do I do about it" line for the common, confusing cases: a host
39+
* that hasn't joined a fleet, and the expected-but-noisy plugin-not-active
40+
* warning that read-only commands emit because they don't load sink
41+
* writer plugins.
42+
*
43+
* @param {{ instance: string, errorKind: string, message: string }} err
44+
* @returns {string | undefined}
45+
*/
46+
function sinkWarningHint(err) {
47+
if (/bootstrap_token is not set/.test(err.message)) {
48+
return "this host hasn't joined a fleet. Run `hyp join <central-url> <token>` to enable the central sink, or ignore this warning if you only capture locally"
49+
}
50+
if (err.errorKind === 'sink_plugin_not_active') {
51+
return `expected for read-only commands (the writer/destination plugin for '${err.instance}' isn't loaded here); the running daemon is unaffected`
52+
}
53+
return undefined
54+
}
55+
3556
/**
3657
* Boot the kernel CLI and dispatch `argv` to a registered command.
3758
*
@@ -88,7 +109,7 @@ export async function dispatch(argv, opts = {}) {
88109
if (argv.length > 0 && VERSION_FLAGS.has(argv[0])) {
89110
const require = createRequire(import.meta.url)
90111
const { version } = require('../../../package.json')
91-
stdout.write(`hyp ${version}\n`)
112+
stdout.write(`hypaware ${version}\n`)
92113
return 0
93114
}
94115
if (argv.length > 0 && HELP_FLAGS.has(argv[0])) {
@@ -132,6 +153,8 @@ export async function dispatch(argv, opts = {}) {
132153
stderr.write(
133154
`warning: sink '${err.instance}' not materialized [${err.errorKind}]: ${err.message}\n`
134155
)
156+
const hint = sinkWarningHint(err)
157+
if (hint) stderr.write(` → ${hint}\n`)
135158
}
136159
}
137160

src/core/commands/backfill.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,14 @@ export async function runBackfill(argv, ctx) {
6969
if (selected.providers.length === 0) {
7070
if (parsed.json) {
7171
ctx.stdout.write(JSON.stringify({ run_id: devRunId, providers: [] }, null, 2) + '\n')
72+
} else if (ctx.backfills.list().length > 0) {
73+
ctx.stdout.write(
74+
'No backfill providers matched your active config. Providers are registered but none are enabled here. Run `hyp backfill list` to see them, or name one explicitly (e.g. `hyp backfill claude`).\n'
75+
)
7276
} else {
73-
ctx.stdout.write('No backfill providers selected. Run `hyp backfill list` for the registered set.\n')
77+
ctx.stdout.write(
78+
'No backfill providers registered. No active plugin contributes one; check enabled plugins with `hyp daemon status`, and if you just joined a fleet the config may still be syncing.\n'
79+
)
7480
}
7581
return 0
7682
}
@@ -157,6 +163,9 @@ export async function runBackfillList(argv, ctx) {
157163
}
158164
if (providers.length === 0) {
159165
ctx.stdout.write('No backfill providers registered.\n')
166+
ctx.stdout.write(
167+
'No active plugin contributes a backfill provider. Check enabled plugins with `hyp daemon status`; if you just joined a fleet, the config may still be syncing.\n'
168+
)
160169
return 0
161170
}
162171
ctx.stdout.write('Backfill providers:\n')

src/core/daemon/runtime.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,26 @@ function clampTickInterval(value) {
557557
return Math.max(value, MIN_TICK_INTERVAL_MS)
558558
}
559559

560+
/**
561+
* Turn a raw source-start error into an operator-actionable message.
562+
* The common failure is a port collision (EADDRINUSE): a second
563+
* HypAware daemon or an unrelated service already holds the gateway
564+
* port. The bare Node string ("listen EADDRINUSE ... 127.0.0.1:8787")
565+
* doesn't say what to do, so this appends the remedy.
566+
*
567+
* @param {unknown} err
568+
* @param {string} source
569+
* @returns {string}
570+
*/
571+
function describeSourceStartError(err, source) {
572+
const base = err instanceof Error ? err.message : String(err)
573+
if (/EADDRINUSE/.test(base)) {
574+
const addr = base.match(/[\d.]+:\d+/)?.[0] ?? 'its configured address'
575+
return `${base}. Source '${source}' could not bind ${addr}; another process (a second HypAware daemon or an unrelated service) already holds it. Stop that process or change the listen address, then restart the daemon.`
576+
}
577+
return base
578+
}
579+
560580
/**
561581
* Start every registered source that has not auto-started during
562582
* `activate()`. Returns one snapshot per source — including the
@@ -612,7 +632,7 @@ async function startConfiguredSources({ runtime, log, fileLog, sourcePluginByNam
612632
details,
613633
})
614634
} catch (err) {
615-
const message = err instanceof Error ? err.message : String(err)
635+
const message = describeSourceStartError(err, contribution.name)
616636
fileLog.error('daemon.source_start_failed', {
617637
source: contribution.name,
618638
plugin,

test/core/cli-version.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,22 @@ test('hyp --version prints version and exits 0', async () => {
4444
const opts = makeDispatchOpts()
4545
const code = await dispatch(['--version'], opts)
4646
assert.equal(code, 0)
47-
assert.equal(opts.stdout.text(), `hyp ${pkg.version}\n`)
47+
assert.equal(opts.stdout.text(), `hypaware ${pkg.version}\n`)
4848
})
4949

5050
test('hyp -V prints version and exits 0', async () => {
5151
const opts = makeDispatchOpts()
5252
const code = await dispatch(['-V'], opts)
5353
assert.equal(code, 0)
54-
assert.equal(opts.stdout.text(), `hyp ${pkg.version}\n`)
54+
assert.equal(opts.stdout.text(), `hypaware ${pkg.version}\n`)
5555
})
5656

5757
test('hyp version prints version info and exits 0', async () => {
5858
const opts = makeDispatchOpts()
5959
const code = await dispatch(['version'], opts)
6060
assert.equal(code, 0)
6161
const out = opts.stdout.text()
62-
assert.ok(out.startsWith(`hyp ${pkg.version}\n`))
62+
assert.ok(out.startsWith(`hypaware ${pkg.version}\n`))
6363
assert.ok(out.includes('node:'))
6464
assert.ok(out.includes('platform:'))
6565
assert.ok(out.includes('hyp_home:'))
@@ -69,6 +69,6 @@ test('version string matches package.json', async () => {
6969
const opts = makeDispatchOpts()
7070
const code = await dispatch(['--version'], opts)
7171
assert.equal(code, 0)
72-
const version = opts.stdout.text().trim().replace('hyp ', '')
72+
const version = opts.stdout.text().trim().replace('hypaware ', '')
7373
assert.equal(version, pkg.version)
7474
})

0 commit comments

Comments
 (0)