Skip to content

pi-config-valid

Pi package and project resource declarations must have valid types

Severity warning (auto)
Autofix -
Since v0.21.0
Repo Types pi, pi-package
Category Pi

Why

Pi reads resource declarations from package.json#pi and project .pi/settings.json, and handles a malformed field differently in each file.

  • .pi/settings.json is not sanitized. A number or object where an array belongs, or a non-string entry, stops Pi at startup with a TypeError. A string is iterated character by character, so "skills": "./x" makes Pi scan the filesystem root.
  • package.json#pi fields that are not arrays of strings are ignored, so that resource type silently loads nothing.

This rule names the field and the expected type before Pi runs.

Checks

  • JSON must parse to an object. Duplicate keys use JavaScript's last-value behavior.
  • The package's pi field must be an object when present.
  • extensions, skills, prompts, and themes must be arrays of strings.
  • Project packages must contain source strings or objects with a nonempty source, optional resource filter arrays, and an optional boolean autoload.
  • null is accepted where Pi reads it as absent: top-level settings resource fields and packages, a package entry's autoload, and pi resource fields. A null package filter still warns, because Pi drops that package.
  • Pi only tests autoload === false, so a non-boolean autoload warns: Pi loads that package as if autoload were on.

Unknown npm fields, Pi gallery metadata, and unrelated settings are accepted. Empty resource lists are valid. Findings are consolidated by file and default to warning, because the same checks cover package.json#pi fields that Pi ignores.

Legacy project settings such as "skills": {"customDirectories": ["../skills"]} are normalized before validation and discovery, as in Pi's settings loader. A nonempty customDirectories array supplies the skill paths; an empty, missing, or non-array value removes the legacy override and leaves autoload active. Malformed array members still warn. This migration applies only to project settings, not package manifests or package selectors.

How to fix

Put paths in arrays, such as "skills": ["./skills"]. In project settings, use "packages": [{"source": "npm:my-package", "skills": []}] to configure a package and disable its skills. Keep settings paths relative to the directory containing settings.json, and package paths relative to package.json.

Examples

Use arrays for resource paths:

{"pi": {"skills": ["./skills"], "prompts": ["./prompts"]}}

A string such as "skills": "./skills" is invalid. In a package manifest it drops that resource type; in .pi/settings.json Pi reads each character as a path and scans from the filesystem root.

Discovery and boundaries

The pi-package repository type comes from a pi key, the pi-package npm keyword, or a repository-local directory listed in project packages. Ordinary npm packages and unmarked conventional directories do not establish Pi ownership. A conventional-only package can be selected through project settings. The pi type comes from .pi project resources, including those in monorepo subprojects.

Portable SKILL.md files not selected by Pi keep their Agent Skills checks. A pi object replaces conventional Pi package discovery; omitted resource types and empty arrays contribute no resources. Without that object, packages use extensions/, skills/, prompts/, and themes/. Manifest globs discover visible paths; ! exclusions, + exact re-inclusions and - exact exclusions filter those resources. Project resource lists resolve literal paths and use wildcards as filters, matching the native loader. Project prompt and theme autoload is shallow; explicit prompt directories and package prompts are recursive.

Locally referenced packages are linted as authored packages, including resources that a particular consumer's package filters disable. Package filters are validated, but do not suppress diagnostics on the package's authored content. Remote sources and paths outside the checkout are not loaded. Glob traversal does not descend through symlinked directories; exact contained roots may use symlinks. Resource walks read .gitignore, .ignore, and .fdignore at or below each resource root, using Pi's directory-relative prefixing.

Pi-only packages receive shared prose checks for README, commands, agents and rules. Claude configuration at hooks/hooks.json or settings.json is checked only when the directory also declares Claude ownership.

Extensions are represented as entrypoint nodes. Themes are configuration nodes, not prose; theme colors and runtime extension behavior are outside this rule. First-party .pi/mcp.json servers receive shared MCP security checks and the opt-in pi-mcp-valid rule.

Upstream reference

Pinned to Pi commit 36b60d2e, particularly packages/coding-agent/src/core/pi-manifest.ts, package-manager.ts, and skills.ts. See the Pi package documentation.

Configuration

rules:
  pi-config-valid:
    enabled: auto  # true | false | auto
    severity: warning

Run skillsaw explain pi-config-valid to see this documentation and the rule's effective configuration in your terminal.