affaan-m/ECC · error
Refusing unsafe repair source metadata: sources must stay…
Error message
Refusing unsafe repair source metadata: sources must stay within the repository.
What it means
Error raised by assertSafeRepairSourcePath in scripts/lib/install-lifecycle.js:148 (via createUnsafeRepairSourceError). Before a repair/hydration operation reads a recorded source file, the path is checked with assertWithinTrustedRoot against the repository root. If the recorded repair source resolves outside the repo (or the trust check itself throws), the lifecycle layer refuses the read with the message that repair sources must stay within the repository, preventing operations recorded with escaped or absolute foreign paths from being replayed.
Solutions
- Inspect the operation record's sourceRelativePath/sourcePath and rewrite it as a path relative to the repository root.
- Confirm the referenced file actually exists inside the repo; re-record the operation if the original file was moved.
- Re-generate the operations manifest with the current ECC tooling instead of editing recorded paths by hand.
- If symlinks are involved, replace them with real in-repo copies so the trust check resolves within the root.
Example fix
// before (recorded operation)
{ "sourceRelativePath": "../../outside/secret-file.md" }
// after
{ "sourceRelativePath": "agents/code-reviewer.md" } Defensive patterns
Strategy: validation
Validate before calling
const path = require('path');
function isWithinRepo(rel, repoRoot) {
if (typeof rel !== 'string') return false;
const resolved = path.resolve(repoRoot, rel);
return resolved.startsWith(path.resolve(repoRoot) + path.sep);
}
if (!isWithinRepo(op.sourceRelativePath, repoRoot)) throw new Error('repair source outside repo'); Type guard
function isSafeRepairSource(op, repoRoot) {
return typeof op.sourceRelativePath === 'string' &&
!op.sourceRelativePath.split(/[/\\]+/).includes('..') &&
path.resolve(repoRoot, op.sourceRelativePath).startsWith(path.resolve(repoRoot));
} Try / catch
try { const src = resolveOperationSourcePath(repoRoot, op); }
catch (e) {
console.error('Recorded repair source is unsafe; re-record the operation with an in-repo relative path.');
process.exitCode = 1;
} Prevention
- Never hand-edit recorded operation manifests; regenerate them with ECC tooling.
- Store only repo-relative paths in sourceRelativePath fields.
- Re-record operations after moving or renaming the repository.
- Avoid symlinks in repair source directories that escape the repo root.
When it happens
Trigger: Replaying or inspecting a recorded install/repair operation whose sourcePath fails assertWithinTrustedRoot — e.g. the recorded path is absolute outside the repo (`/etc/...`, `C:\...`), or the trust check throws (non-existent path, symlink escape) and the catch re-raises as the unsafe-repair-source error.
Common situations: A manifest/record file was hand-edited or generated by an older tool version storing absolute paths; the repo was moved/renamed so previously relative-trusted paths now resolve outside the new root; symlinked source directories pointing outside the repository.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- artifact path escapes output directory
- output path contains an invalid component
- spec.file must keep generated files directly inside the…
- artifact path must stay beneath output root
- artifact permits provider execution
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/4d3a5c73095f53cc.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install-lifecycle.js:148
const stdout = typeof error.stdout === 'string' ? error.stdout.trim() : '';
return stderr || stdout || error.message || 'Failed to build OpenCode payload';
}
function getManagedOperations(state) {
return Array.isArray(state && state.operations) ? state.operations.filter(operation => operation.ownership === 'managed') : [];
}
function createUnsafeRepairSourceError() {
return new Error(
'Refusing unsafe repair source metadata: sources must stay within the repository.'
);
}
function assertSafeRepairSourcePath(sourcePath, repoRoot) {
try {
return assertWithinTrustedRoot(sourcePath, repoRoot, 'read repair source');
} catch {
throw createUnsafeRepairSourceError();
}
}
function resolveOperationSourcePath(repoRoot, operation) {
if (operation.sourceRelativePath) {
if (typeof operation.sourceRelativePath !== 'string') {
throw createUnsafeRepairSourceError();
}
const sourceRelativePath = operation.sourceRelativePath;
const hasParentTraversal = sourceRelativePath
.split(/[/\\]+/)
.includes('..');
const isAbsolute = path.isAbsolute(sourceRelativePath)
|| path.win32.isAbsolute(sourceRelativePath);
if (isAbsolute || hasParentTraversal) {
throw createUnsafeRepairSourceError();
}View on GitHub (pinned to 8321021c54)