github/spec-kit · error · Error

specify event ${{command}} (${{event}}) failed: ${{(e as Err

Error message

specify event ${{command}} (${{event}}) failed: ${{(e as Error).message}}

What it means

Raised by _shared_relative_path() in specify_cli/shared_infra.py when a shared-infrastructure destination path cannot be expressed relative to the project root (Path.relative_to raises ValueError). The shared-infra layer only writes inside the project tree, so an absolute destination pointing elsewhere, or a path built from a different root, is rejected before any filesystem write happens.

Source

Thrown at src/specify_cli/events.py:443

  try {{
    // execFileSync with an argv array invokes the interpreter directly — no
    // shell — so command/event strings with metacharacters can't break out
    // of the dispatcher argument (C9). The dispatcher arg is seconds; the
    // execFileSync timeout is ms with a buffer so the outer cap fires after
    // the dispatcher's inner subprocess (S3). stdout is captured and
    // returned so context-injection hooks (experimental.chat.system.transform,
    // chat.message) can push it into their outputs; stderr stays inherited so
    // dispatcher errors remain visible (C11).
    return execFileSync(INTERPRETER, [DISPATCHER, command, event, String(timeoutSec)], {{
      input: JSON.stringify({{ input, output }}),
      stdio: ['pipe', 'pipe', 'inherit'],
      encoding: 'utf-8',
      timeout: (timeoutSec + {buffer}) * 1000,
    }});
  }} catch (e) {{
    // Propagate to OpenCode's hook machinery so only this hook is rejected,
    // not the entire host process. process.exit() would kill the agent.
    throw new Error(`specify event ${{command}} (${{event}}) failed: ${{(e as Error).message}}`);
  }}
}}

// Cache session_start handler output per sessionID so non-idempotent
// handlers (setup, telemetry, file-mutating scripts) run once per session
// instead of on every LLM request (experimental.chat.system.transform
// fires per LLM turn). Evicted on session.deleted.
const sessionStartCache = new Map<string, string>();

{event_entries}

export default (async ({{ client, project, directory, $ }}) => {{
  resolveDispatcher(directory);
  return {{
{plugin_returns}
  }};
}});
'''

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Ensure dest is constructed by joining project_path: dest = project_path / relative_subpath, never an absolute path from elsewhere
  2. If dest comes from user input, strip a leading '/' or reject absolute paths before calling the API
  3. If the write legitimately targets another tree, call the API with that tree's root as project_path instead
  4. In tests, derive all paths from the same tmp_path instance

Example fix

// before
_ensure_safe_shared_destination(Path.cwd(), Path('/opt/data/workflow.yaml'))

// after
_ensure_safe_shared_destination(Path.cwd(), Path.cwd() / '.specify' / 'workflows' / 'workflow.yaml')
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def is_inside_root(project: Path, dest: Path) -> bool:
    try:
        dest.relative_to(project)
        return True
    except ValueError:
        return False

# before calling shared-infra write helpers:
assert is_inside_root(project_path, dest), f'dest outside project: {dest}'

Try / catch

try:
    _ensure_safe_shared_destination(project_path, dest)
except ValueError as e:
    if 'escapes project root' in str(e):
        dest = project_path / safe_relative_from(dest)
    else:
        raise

Prevention

When it happens

Trigger: Calling _write_shared_bytes/_write_shared_text/_ensure_safe_shared_destination (directly or via install_shared_infra/preset writers) with a dest that is absolute and outside project_path, or a relative dest that was joined against another base. Example: _ensure_safe_shared_destination(Path('/repo'), Path('/etc/passwd')).

Common situations: Passing a user-supplied config path (e.g. from a config file or CLI arg) that is absolute or anchored to a different checkout; running the CLI from a symlinked or misidentified working directory so project_path and dest disagree; tests using tmp_path fixtures that mix two different temp roots.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/9a85b56308d64f91. Report an issue: GitHub.