ruvnet/ruflo · error · Error
repository harness descriptor requires a non-empty harnessId
Error message
repository harness descriptor requires a non-empty harnessId
What it means
normalizeHarnessDescriptor accepts either a modern HarnessDescriptor (harnessId) or a legacy descriptor (name) and downgrades it to the safe v1 baseline. The id resolution (input.harnessId ?? input.name) must yield a non-empty trimmed string; undefined, '', or whitespace-only values throw. The function deliberately strips 'release-decisions' and unknown capabilities — this error is purely about the missing identifier.
Source
Thrown at v3/@claude-flow/codex/src/harness/contract.ts:196
'message-acknowledgement',
'run-evidence',
'exact-source-state',
'release-decisions',
]);
/**
* Convert an older descriptor to the safe v1 reference baseline.
*
* Unknown and authority-bearing capabilities are ignored. A legacy descriptor
* can never self-assert enforce or release authority through this compatibility
* function. A separately verified external adapter is required for that.
*/
export function normalizeHarnessDescriptor(
input: HarnessDescriptor | LegacyHarnessDescriptor,
): InMemoryReferenceHarnessDescriptor {
const harnessId = input.harnessId ?? ('name' in input ? input.name : undefined);
if (typeof harnessId !== 'string' || harnessId.trim().length === 0) {
throw new Error('repository harness descriptor requires a non-empty harnessId');
}
const requested = Array.isArray(input.capabilities) ? input.capabilities : [];
const capabilities: Array<Exclude<HarnessCapability, 'release-decisions'>> = [...new Set(
requested.filter((value): value is HarnessCapability => (
CAPABILITIES.has(value as HarnessCapability)
&& value !== 'release-decisions'
)),
)] as Array<Exclude<HarnessCapability, 'release-decisions'>>;
capabilities.sort();
return {
contractVersion: 1,
harnessId: harnessId.trim(),
mode: 'observe',
capabilities,
advisory: true,
assurance: 'in-memory-reference',View on GitHub (pinned to fa13ee4ad6)
Solutions
- Set harnessId to a stable non-empty identifier on the descriptor
- For legacy descriptors, populate the name field — it is accepted as the id
- Validate descriptors right after deserialization and before calling normalizeHarnessDescriptor so failures carry file/field context
Example fix
// before
const descriptor = { capabilities: ['coordination'] }; // no harnessId or name
normalizeHarnessDescriptor(descriptor); // throws
// after
const descriptor = { harnessId: 'my-harness', capabilities: ['coordination'] };
// or the legacy shape:
const legacy = { name: 'my-harness', capabilities: ['coordination'] }; Defensive patterns
Strategy: validation
Validate before calling
function hasHarnessIdentifier(d: Partial<{ harnessId: string; name: string }>): boolean {
const id = d.harnessId ?? d.name;
return typeof id === 'string' && id.trim().length > 0;
} Type guard
function isDescriptorWithId(d: unknown): d is { harnessId: string } | { name: string } {
if (typeof d !== 'object' || d === null) return false;
const rec = d as Record<string, unknown>;
const id = rec.harnessId ?? rec.name;
return typeof id === 'string' && id.trim().length > 0;
} Try / catch
Catch around normalizeHarnessDescriptor during config load and report the source file/key of the offending descriptor; fail startup rather than defaulting the id, since a blank id would make harness sessions unaddressable.
Prevention
- Validate descriptors immediately after JSON/YAML deserialization
- Make harnessId required in your own schema (with name as a legacy alias) so TypeScript catches omissions
- Watch for spreads of default objects that can overwrite harnessId with ''
When it happens
Trigger: Passing a descriptor with neither harnessId nor name; harnessId set to '' or ' '; a legacy shape whose name key was removed; a spread of defaults that overwrites harnessId with an empty string.
Common situations: Loading harness descriptors from JSON configs where the key was renamed from name to harnessId and older files lack the new field; optional-chaining chains producing undefined; YAML parsers turning an empty value into ''.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- ${label} must be non-empty
- ${label} must be a canonical sha256 digest
- build evidence path is not a file or symlink: ${path}
- duplicate declared build input
- duplicate declared toolchain
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/745df4db44f2d31e.
Report an issue: GitHub.