affaan-m/ECC · error · Error

must be a non-empty string

Error message

${label} must be a non-empty string

What it means

ensureString is a shared guard in the observations telemetry module: the value must be a string whose trimmed length is non-zero, otherwise the error is thrown with the parameter label embedded. It returns the trimmed value. It is used to validate identifiers like task and skillId before writing observation records.

Solutions

  1. Provide a non-empty string for the flagged field (see label in the message)
  2. Trim and check the value before calling: if (!value || !value.trim()) handle it upstream
  3. Fix the config/CLI plumbing so the field is actually populated
  4. If the field is genuinely optional, pass a sensible default like 'unknown' instead of an empty string

Example fix

// before
recordObservation({ task: cfg.taskName ?? '', skillId }); // throws when unset
// after
const task = (cfg.taskName ?? '').trim();
if (!task) throw new Error('taskName is required in config');
recordObservation({ task, skillId });
Defensive patterns

Strategy: type-guard

Validate before calling

function isNonEmptyString(v) {
  return typeof v === 'string' && v.trim().length > 0;
}
if (!isNonEmptyString(task) || !isNonEmptyString(skillId)) {
  throw new Error('task and skillId are required non-empty strings');
}

Type guard

const isNonEmptyString = (v) => typeof v === 'string' && v.trim().length > 0;

Try / catch

try {
  recordObservation({ task, skillId });
} catch (err) {
  if (err.message.endsWith('must be a non-empty string')) {
    console.warn(`Skipping observation: ${err.message}`);
    return null;
  } throw err;
}

Prevention

When it happens

Trigger: Calling observation APIs with task or skillId set to undefined, null, an empty string, or a whitespace-only string like ' '; also passing a non-string (number, object) which fails the typeof check.

Common situations: Optional config field left unset so the value stays undefined; template-literal interpolation producing '' when an upstream variable is empty; extracting task names from a CLI flag the user never supplied; refactors changing a field name so the old variable no longer exists.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/6e5fa0714e1cbd77. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/skill-improvement/observations.js:23

const os = require('os');

const OBSERVATION_SCHEMA_VERSION = 'ecc.skill-observation.v1';

function resolveProjectRoot(options = {}) {
  return path.resolve(options.projectRoot || options.cwd || process.cwd());
}

function getSkillTelemetryRoot(options = {}) {
  return path.join(resolveProjectRoot(options), '.claude', 'ecc', 'skills');
}

function getSkillObservationsPath(options = {}) {
  return path.join(getSkillTelemetryRoot(options), 'observations.jsonl');
}

function ensureString(value, label) {
  if (typeof value !== 'string' || value.trim().length === 0) {
    throw new Error(`${label} must be a non-empty string`);
  }

  return value.trim();
}

function createObservationId() {
  return `obs-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2, 8)}`;
}

function createSkillObservation(input) {
  const task = ensureString(input.task, 'task');
  const skillId = ensureString(input.skill && input.skill.id, 'skill.id');
  const skillPath = typeof input.skill.path === 'string' && input.skill.path.trim().length > 0
    ? input.skill.path.trim()
    : null;
  const success = Boolean(input.success);
  const error = input.error === null || input.error === undefined ? null : String(input.error);
  const feedback = input.feedback === null || input.feedback === undefined ? null : String(input.feedback);

View on GitHub (pinned to 8321021c54)