Skip to content

fix(config): honor documented PI_RULES_* env vars - #25

Open
Yoonkeee wants to merge 6 commits into
code-yeongyu:mainfrom
Yoonkeee:fix/honor-pi-rules-env-vars
Open

fix(config): honor documented PI_RULES_* env vars#25
Yoonkeee wants to merge 6 commits into
code-yeongyu:mainfrom
Yoonkeee:fix/honor-pi-rules-env-vars

Conversation

@Yoonkeee

@Yoonkeee Yoonkeee commented Jul 31, 2026

Copy link
Copy Markdown

Fixes #24

Summary

  • PI_RULES_DISABLED, PI_RULES_MAX_RULE_CHARS and PI_RULES_MAX_RESULT_CHARS are documented in five places, but grep -rn "process\.env" src/ returned zero matches — src/index.ts built its config from defaultConfig() unconditionally, so all three were inert. The engine already honored custom values; only the resolution step was missing.
  • New src/config.ts exports configFromEnvironment(), following codex-rules' src/config.ts (same PiRulesConfig shape, same isTruthy set). Two deliberate deviations: firstEnv collapsed to readEnv (pi-rules has no alias names to fall back to), and parsePositiveInteger validates the whole string instead of using bare Number.parseInt, so "50abc" / "1.5" / "1e3" fall back to the default rather than silently becoming 50 / 1 / 1. That keeps the promise in the new JSDoc and in Documented PI_RULES_DISABLED / PI_RULES_MAX_RULE_CHARS / PI_RULES_MAX_RESULT_CHARS env vars are never read #24 that a typo degrades to the documented default; since these variables were previously inert there is no back-compat cost.
  • syncConfigFromFlags() now composes the flag with the env baseline (disabled || envDisabled) instead of overwriting it. pi applies boolean extension flags as presence-only (agent-session-services.js: if (flag.type === "boolean") flagValues.set(name, true)), so getFlag("pi-rules-disabled") returns the registered default false whenever the flag was not passed — resolving env only at construction time would have been silently clobbered on the very first hook.
  • Because src/index.ts now reads the real process.env, a vitest setup file clears the three variables so the pre-existing suites stay hermetic when a contributor has them exported in their shell (verified: without it, PI_RULES_DISABLED=1 npm test broke unrelated tests).
  • Scope is limited to the three documented variables. codex-rules also reads PI_RULES_MODE and PI_RULES_ENABLED_SOURCES; those are undocumented here, so I left them out rather than smuggle in new features — happy to follow up if you want full parity.
  • README.md needs no wording change: "If 1, disables injection" stays true (the accepted truthy set 1/true/yes/on is a superset), and the documented 12000 / 40000 defaults are unchanged.

RED-first evidence

test/env-config.test.ts before the src/index.ts change (3 failed | 3 passed):

× … #then the injected block respects the caps and carries the truncation notice
  AssertionError: expected 5128 to be less than or equal to 200

× #given PI_RULES_DISABLED=1 … #then before_agent_start returns undefined
  AssertionError: expected { Object (systemPrompt) } to be undefined

× #given PI_RULES_DISABLED=1 #when tool_result is emitted … #then the handler returns undefined
  AssertionError: expected { content: [ { type: 'text', …(1) } ] } to be undefined

test/config.test.ts before src/config.ts existed:

FAIL  test/config.test.ts [ test/config.test.ts ]
Error: Cannot find module '../src/config.js' imported from …/test/config.test.ts

Verification

  • npm run check (typecheck + biome) — exit 0 (only the pre-existing biome deprecated-config info)
  • npm test (unit tests) — 268 passed / 17 files (252 pre-existing + 16 new, 0 skipped)
  • npm run test:integration (integration tests) — 46 passed / 4 files
  • npm pack --dry-run (release sanity) — 24 files, src/config.ts included
  • pi -e ./src/index.ts smoke-tested locally, if behavior changed

The CI matrix is currently waiting on maintainer approval (first-time fork contributor), so I reproduced it locally: npm ci + all four commands on Node 22.21.0 and 24.17.0 — check PASS, 268 unit, 46 integration, pack PASS on both.

End-to-end against the real pi binary

Unit tests alone cannot prove the wiring reaches a real session, so I drove the actual pi CLI once per permutation with pi-rules loaded (pi -e ./src/index.ts -e <mock-provider> --provider mocklocal --model mock-1 -p "hello"), pointing it at a local OpenAI-compatible endpoint that records the system prompt pi really sends. Fixture: .omo/rules/big.md with alwaysApply: true and a 5,000-char body, isolated HOME.

# environment / flag injected? block chars truncated rule body
01 (none — baseline) yes 5120 no 5034
02 MAX_RULE_CHARS=50 MAX_RESULT_CHARS=200 yes 139 yes 53
03 DISABLED=1 no 0 0
04 MAX_RULE_CHARS=abc MAX_RESULT_CHARS=-5 yes 5120 no 5034
05 DISABLED=0 yes 5120 no 5034
06 --pi-rules-disabled (flag only) no 0 0
07 DISABLED=true no 0 0
08 DISABLED=yes no 0 0
09 MAX_RULE_CHARS=100 yes 186 yes 100
10 DISABLED=1 + --pi-rules-disabled no 0 0
11 MAX_RESULT_CHARS=300 (no per-rule cap) yes 298 yes 212
12 MAX_RULE_CHARS=50abc yes 5120 no 5034
13 MAX_RULE_CHARS=1.5 yes 5120 no 5034
14 MAX_RESULT_CHARS=1e3 yes 5120 no 5034

Cases 09 and 11 show each cap taking effect on its own; 12–14 show the stricter parser refusing partially-numeric input instead of silently truncating to 50 / 1 / 1.

Verified in the same live run: the extension loads in the TUI ([Extensions] src) and /rules status answers pi-rules: 2 rules from 2 sources.

Mutation checks on the new tests

To confirm the tests actually pin the wiring rather than passing incidentally, I removed one line at a time and re-ran them:

removed from src/config.ts failing test assertion
maxResultChars resolution per-result cap test only expected 5128 to be less than or equal to 223
maxRuleChars resolution per-rule cap test only expected … to have a length of 50 but got 5000
strict integer validation malformed-value test only expected 1 to be 12000

Rule-loading impact

  • Rule discovery / precedence documented in README if changed — unchanged; discovery, precedence and the env-var table all stay as documented
  • TUI widget behavior documented if changed — unchanged
  • CHANGELOG entry added under [Unreleased] if user-facing

Atomic commits

  • de1cd02 fix(config): resolve documented PI_RULES_* env vars — new src/config.ts + unit tests, no consumer yet
  • 17e566c fix(config): apply env config at startup and keep disabled across flag sync — wiring + the flag-composition fix, with its integration tests
  • 5562cd1 docs: align changelog and jsdoc with env var support — CHANGELOG entry; PiRulesConfig JSDoc no longer claims package.json resolution, which does not exist
  • 983c05e fix(config): reject malformed numeric env values — full-string integer validation
  • 2d1e52b test(config): cover per-rule and per-result caps separately — the earlier single test proved only the per-rule cap and its bound depended on the absolute rule path
  • a525477 test: isolate suites from ambient PI_RULES_* env — vitest setup file so existing suites stay hermetic

One thing I noticed but did not touch

While writing the tool_result test I found that test/extension-registration.test.ts's #given disabled true #when tool_result emitted and #given mode="static" #when tool_result emitted currently assert vacuously: project.write() returns the symlinked /var/folders/… path while cwd is realpathSync.native'd to /private/var/folders/…, so extractToolPathsfindProjectRoot never matches and the handler returns undefined regardless of the flag. My own test wraps the target in realpathSync.native() so it exercises the real path. Left the existing tests alone since it is out of scope here — happy to send a separate PR if you want them tightened.

@Yoonkeee
Yoonkeee requested a review from code-yeongyu as a code owner July 31, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Documented PI_RULES_DISABLED / PI_RULES_MAX_RULE_CHARS / PI_RULES_MAX_RESULT_CHARS env vars are never read

1 participant