---
issue: 6
issue_title: "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 single `config.resolved` review-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 on `PermissionManager` so 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` — `PermissionManager` constructor receives `globalConfigPath`, `agentsDir`, `projectGlobalConfigPath`, `projectAgentsDir`, plus internally computes `legacyGlobalSettingsPath` and `globalMcpConfigPath`.
  The paths are stored as private fields but are not externally queryable.
- `src/extension-config.ts` — `CONFIG_PATH` is the extension's own `config.json`.
  `loadPermissionSystemConfig` loads it and returns a result with optional warning.
- `src/index.ts` — `piPermissionSystemExtension` wires everything together.
  `refreshExtensionConfig()` runs at module load and on every `session_start`.
  `createPermissionManagerForCwd()` constructs the `PermissionManager` with project-scoped paths derived from `ctx.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

```typescript
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.

```typescript
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:

1. Calls `permissionManager.getResolvedPolicyPaths()`.
2. Adds `extensionConfigPath` / `extensionConfigExists` from `CONFIG_PATH`.
3. Writes a single `config.resolved` entry to both `writeReviewLog` and `writeDebugLog`.

### Log entry format

```jsonc
{
  "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

- `projectConfigPath` and `projectAgentsDir` are `null` when no `cwd` is available — logged as `null` with `*Exists: false`.
- Extension config file missing (first run, `ensurePermissionSystemConfig` creates it) — `extensionConfigExists` reflects state *after* the ensure step, so it will be `true`.
- Multiple `session_start` events (reload) — the entry is emitted each time, which is correct since `cwd` and thus project paths may change.

## Module-Level Changes

### `src/permission-manager.ts`

1. Add `ResolvedPolicyPaths` interface (exported).
2. Add public `getResolvedPolicyPaths(): ResolvedPolicyPaths` method that returns the five path pairs using `existsSync`.

### `src/index.ts`

1. Import `existsSync` (already imported) and `CONFIG_PATH` (already imported).
2. Add `logResolvedConfigPaths(pm: PermissionManager): void` helper that assembles the full entry and writes to both review and debug logs.
3. Call `logResolvedConfigPaths(permissionManager)` in the `session_start` handler after `permissionManager` is created.

### `tests/permission-manager.test.ts` (existing or new)

1. 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)

1. 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.resolved` entry 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

1. **Red:** test that `getResolvedPolicyPaths()` returns correct paths and existence flags when all policy files exist.
   `test: getResolvedPolicyPaths returns paths and existence when files exist`

2. **Green:** implement `ResolvedPolicyPaths` interface and `getResolvedPolicyPaths()` on `PermissionManager`.
   `feat: add getResolvedPolicyPaths to PermissionManager (#6)`

3. **Red:** test that `getResolvedPolicyPaths()` returns `*Exists: false` when files are missing and `null` for absent project paths.
   `test: getResolvedPolicyPaths handles missing files and null project paths`

4. **Green:** already passes from step 2 (or adjust).

5. **Red:** test the log-entry assembly helper produces the expected `config.resolved` shape combining extension config path with policy paths.
   `test: config.resolved log entry merges extension and policy paths`

6. **Green:** implement `buildResolvedConfigLogEntry()` helper in `index.ts` (or a small `src/config-reporter.ts` if warranted) and wire into `session_start`.
   `feat: emit config.resolved review-log entry at startup (#6)`

7. **Red:** integration test verifying the review log receives a `config.resolved` entry after a simulated startup sequence.
   `test: config.resolved entry appears in review log after session_start`

8. **Green:** already passes from step 6 (or adjust wiring).

9. **Docs:** mention the `config.resolved` log entry in `README.md` under 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.ts` module or keep it inline in `index.ts`.
  Decision can be made during implementation based on size.
