OpenNHP/opennhp · error

value not set

Error message

value not set

What it means

Raised by EtcdConn.GetValue when the etcd Get for conn.Key succeeds and the key exists, but the stored value is a zero-length byte slice. It means the key is present in etcd yet was written with an empty payload (e.g. someone PUT the key with no value), so the caller cannot use it as configuration. Distinct from the 'key not found' error raised a few lines above for a missing key.

Solutions

  1. Write the actual config value to the key (etcdctl put <key> '<non-empty json>')
  2. Fix the writer/template that produced the empty value
  3. Add a pre-start check that the key's value is non-empty before starting daemons
  4. Check deployment ordering: seed etcd before starting AC/server

Example fix

// before
etcdctl put /opennhp/server/config ''
// after
etcdctl put /opennhp/server/config '{"logLevel":"info"}'
Defensive patterns

Strategy: validation

Validate before calling

resp, _ := etcdCli.Get(ctx, key)
if resp.Count > 0 && len(resp.Kvs[0].Value) == 0 {
    return fmt.Errorf("etcd key %s exists but is empty", key)
}

Try / catch

val, err := conn.GetValue()
if err != nil {
    if err.Error() == "value not set" {
        return fmt.Errorf("repopulate config for key %q", conn.Key)
    }
    return err
}

Prevention

When it happens

Trigger: GetValue (via loadRemoteConfig/loadRemoteBaseConfig) reads a key that was created with an empty string (etcdctl put <key> ""), or a writer overwrote the config with empty content.

Common situations: Deployment script quoting bug writing an empty value; config cleared during rotation but consumers started before it was repopulated; JSON templating rendered to an empty string.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/8bca09146c6b12ff. Report an issue: GitHub.

Appendix: source

Thrown at nhp/etcd/etcdconn.go:66

	conn.ctx = ctx
	_, err = conn.client.Status(conn.ctx, conn.Endpoints[0])
	if err != nil {
		return err
	}
	return nil
}

func (conn *EtcdConn) GetValue() ([]byte, error) {
	val, err := conn.client.Get(conn.ctx, conn.Key)
	if err != nil {
		return nil, err
	}

	if len(val.Kvs) == 0 {
		return nil, errors.New("key not found")
	}
	if len(val.Kvs[0].Value) == 0 {
		return nil, errors.New("value not set")
	}
	return val.Kvs[0].Value, nil
}

func (conn *EtcdConn) SetValue(v string) error {
	_, err := conn.client.Put(conn.ctx, conn.Key, v)
	return err
}

func (conn *EtcdConn) WatchValue(callbackFunc func(val []byte)) {
	// create etcd watcher
	conn.watcher = clientv3.NewWatcher(conn.client)

	watchChan := conn.watcher.Watch(context.Background(), conn.Key)

	for {
		select {
		case resp := <-watchChan:

View on GitHub (pinned to 6e04ca5ff0)