9.6 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 6 | Log resolved config paths at startup so misconfiguration is debuggable |
Log resolved config paths at startup
Problem Statement
There is no way for a user to see which permission-config files the extension actually loaded. When permissions do not work as expected, debugging requires reading source to figure out the search paths. A single review-log entry listing all resolved paths and their existence status would unblock most debugging.
Goals
- At extension startup (and on each
session_start), emit a singleconfig.resolvedreview-log entry listing every config path the extension considers and whether each file exists. - Also emit the entry to the debug log when debug logging is enabled.
- Expose a
getResolvedConfigPaths()method onPermissionManagerso the path set is testable without IO side effects. - The log entry appears regardless of whether any rules are ultimately matched.
Non-Goals
- A
pi config-style TUI view (mentioned in the issue as a bonus; separate work). - Changing any policy semantics, merge precedence, or on-disk identity.
- Logging the contents of each config file (only paths and existence).
Background
Relevant modules
src/permission-manager.ts—PermissionManagerconstructor receivesglobalConfigPath,agentsDir,projectGlobalConfigPath,projectAgentsDir, plus internally computeslegacyGlobalSettingsPathandglobalMcpConfigPath. The paths are stored as private fields but are not externally queryable.src/extension-config.ts—CONFIG_PATHis the extension's ownconfig.json.loadPermissionSystemConfigloads it and returns a result with optional warning.src/index.ts—piPermissionSystemExtensionwires everything together.refreshExtensionConfig()runs at module load and on everysession_start.createPermissionManagerForCwd()constructs thePermissionManagerwith project-scoped paths derived fromctx.cwd.src/logging.ts—PermissionSystemLogger.review()and.debug()write structured JSONL lines.
Permission surface
This is a diagnostic/observability concern. No permission surface (tools / bash / mcp / skills / special / external_directory) is changed. No policy semantics or merge precedence is affected.
Prerequisites
- Issue #4 (warn on misplaced permission keys in
config.json) is already implemented and closed. This plan builds on the same startup path but is independent.
Design Overview
Data shape
interface ResolvedConfigPaths {
extensionConfigPath: string;
extensionConfigExists: boolean;
globalConfigPath: string;
globalConfigExists: boolean;
projectConfigPath: string | null;
projectConfigExists: boolean;
agentsDir: string;
agentsDirExists: boolean;
projectAgentsDir: string | null;
projectAgentsDirExists: boolean;
}
New method on PermissionManager
Add a public getResolvedConfigPaths(): ResolvedConfigPaths method that returns the struct above.
It checks existsSync for each path (cheap, synchronous, already imported in the file).
The method is a pure query — no caching side effects.
extensionConfigPath is not owned by PermissionManager; it will be passed in by the caller (or injected via a new optional constructor option) since the extension config path lives in extension-config.ts.
To keep the method self-contained without adding a constructor dependency, the caller in index.ts will combine the PermissionManager paths with the extension config path when building the log entry.
Revised approach: add getResolvedPolicyPaths() to PermissionManager (returns only the policy-related paths it owns), and have the startup reporter in index.ts merge in the extension config path to produce the full config.resolved log entry.
interface ResolvedPolicyPaths {
globalConfigPath: string;
globalConfigExists: boolean;
projectConfigPath: string | null;
projectConfigExists: boolean;
agentsDir: string;
agentsDirExists: boolean;
projectAgentsDir: string | null;
projectAgentsDirExists: boolean;
}
Where the log entry is emitted
In index.ts, after permissionManager is (re)created in the session_start handler, call a new helper logResolvedConfigPaths(permissionManager) that:
- Calls
permissionManager.getResolvedPolicyPaths(). - Adds
extensionConfigPath/extensionConfigExistsfromCONFIG_PATH. - Writes a single
config.resolvedentry to bothwriteReviewLogandwriteDebugLog.
Log entry format
{
"event": "config.resolved",
"extensionConfigPath": "/…/pi-permission-system/config.json",
"extensionConfigExists": true,
"globalConfigPath": "/…/.pi/agent/pi-permissions.jsonc",
"globalConfigExists": false,
"projectConfigPath": "/…/my-project/.pi/agent/pi-permissions.jsonc",
"projectConfigExists": true,
"projectConfigExists": true,
"agentsDir": "/…/.pi/agent/agents",
"agentsDirExists": true,
"projectAgentsDir": "/…/my-project/.pi/agent/agents",
"projectAgentsDirExists": false
}
Edge cases
projectConfigPathandprojectAgentsDirarenullwhen nocwdis available — logged asnullwith*Exists: false.- Extension config file missing (first run,
ensurePermissionSystemConfigcreates it) —extensionConfigExistsreflects state after the ensure step, so it will betrue. - Multiple
session_startevents (reload) — the entry is emitted each time, which is correct sincecwdand thus project paths may change.
Module-Level Changes
src/permission-manager.ts
- Add
ResolvedPolicyPathsinterface (exported). - Add public
getResolvedPolicyPaths(): ResolvedPolicyPathsmethod that returns the five path pairs usingexistsSync.
src/index.ts
- Import
existsSync(already imported) andCONFIG_PATH(already imported). - Add
logResolvedConfigPaths(pm: PermissionManager): voidhelper that assembles the full entry and writes to both review and debug logs. - Call
logResolvedConfigPaths(permissionManager)in thesession_starthandler afterpermissionManageris created.
tests/permission-manager.test.ts (existing or new)
- Add tests for
getResolvedPolicyPaths()covering: all paths exist, none exist, mixed, null project paths.
tests/index.test.ts or tests/config-resolved-log.test.ts (new)
- Add tests for the log-entry assembly helper (if extracted as a pure function) or integration-level tests verifying the review log contains a
config.resolvedentry after startup.
No changes needed
schemas/permissions.schema.json— no policy-schema change.config/config.example.json— no extension-config change.README.md— could mention the diagnostic log entry, but optional and can be a follow-up.
TDD Order
-
Red: test that
getResolvedPolicyPaths()returns correct paths and existence flags when all policy files exist.test: getResolvedPolicyPaths returns paths and existence when files exist -
Green: implement
ResolvedPolicyPathsinterface andgetResolvedPolicyPaths()onPermissionManager.feat: add getResolvedPolicyPaths to PermissionManager (#6) -
Red: test that
getResolvedPolicyPaths()returns*Exists: falsewhen files are missing andnullfor absent project paths.test: getResolvedPolicyPaths handles missing files and null project paths -
Green: already passes from step 2 (or adjust).
-
Red: test the log-entry assembly helper produces the expected
config.resolvedshape combining extension config path with policy paths.test: config.resolved log entry merges extension and policy paths -
Green: implement
buildResolvedConfigLogEntry()helper inindex.ts(or a smallsrc/config-reporter.tsif warranted) and wire intosession_start.feat: emit config.resolved review-log entry at startup (#6) -
Red: integration test verifying the review log receives a
config.resolvedentry after a simulated startup sequence.test: config.resolved entry appears in review log after session_start -
Green: already passes from step 6 (or adjust wiring).
-
Docs: mention the
config.resolvedlog entry inREADME.mdunder a debugging/troubleshooting section.docs: document config.resolved diagnostic log entry (#6)
Risks and Mitigations
| Risk | Mitigation |
|---|---|
existsSync on every session_start adds latency |
Five synchronous stat calls are negligible; no mitigation needed. |
| Could this silently weaken a permission? | No — this change only adds a diagnostic log entry. Permission resolution logic is untouched. |
| Log entry leaks sensitive path information | Paths are already visible in the debug log and are user-controlled config locations; no new exposure. |
getResolvedPolicyPaths exposes internal paths of PermissionManager |
The paths are user-configured inputs, not secrets. Exposing them is the explicit goal. |
Multiple session_start handlers already exist in index.ts |
The log call will be added to the existing handler, not a new duplicate. |
Open Questions
- Whether to extract the log-entry builder into a standalone
src/config-reporter.tsmodule or keep it inline inindex.ts. Decision can be made during implementation based on size.