affaan-m/ECC · error

${label} must be a non-empty string

Error message

${label} must be a non-empty string

What it means

Thrown by ensureString() in the skill-improvement observations module, invoked from createSkillObservation() for the 'task' and 'skill.id' fields. The helper rejects anything that is not a string or is empty/whitespace-only, because observation records must be uniquely attributable to a task and a skill identifier for downstream telemetry grouping. The label in the message identifies which field failed.

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 01e15490f0)

Solutions

  1. Ensure input.task is a non-empty trimmed string before calling createSkillObservation.
  2. Ensure input.skill exists and input.skill.id is a non-empty trimmed string.
  3. Default or reject early at your API boundary: if (!task || !skill?.id) return null; rather than letting ensureString throw deep inside the library.
  4. Add a runtime type check (e.g. zod schema) for the observation payload at ingress.

Example fix

// before
createSkillObservation({ task: '', skill: { id: 'tdd-workflow' }, success: true });
// -> task must be a non-empty string

// after
if (!input.task?.trim() || !input.skill?.id?.trim()) return null;
createSkillObservation({
  task: input.task.trim(),
  skill: { id: input.skill.id.trim(), path: input.skill.path },
  success: true,
});
Defensive patterns

Strategy: validation

Validate before calling

function isValidObservationInput(input) {
  return !!(
    input &&
    typeof input.task === 'string' && input.task.trim().length > 0 &&
    input.skill &&
    typeof input.skill.id === 'string' && input.skill.id.trim().length > 0
  );
}

if (!isValidObservationInput(input)) return null;
createSkillObservation(input);

Type guard

function isNonEmptyString(value) {
  return typeof value === 'string' && value.trim().length > 0;
}

function isObservationInput(value) {
  return !!(
    value &&
    isNonEmptyString(value.task) &&
    value.skill &&
    isNonEmptyString(value.skill.id)
  );
}

Try / catch

try {
  createSkillObservation(input);
} catch (error) {
  if (/must be a non-empty string/.test(error.message)) {
    // drop the observation rather than crash the telemetry pipeline
    return null;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling createSkillObservation({ task: '', skill: { id: 'foo' } }); calling it with task undefined; passing skill.id as null; passing an object whose skill property is missing entirely (input.skill && input.skill.id short-circuits to undefined); passing a numeric id like 42.

Common situations: CLI forwards an optional --task flag that was omitted; an upstream telemetry hook fires before the skill id was resolved; a JSON payload from a queue has null for empty fields instead of empty strings; refactoring renames skill.id to skill.name and forgets the call site.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/6e5fa0714e1cbd77. Report an issue: GitHub.