fix(coding-agent): resolve workflow settings under the config root - #3327
Conversation
Yeachan-Heo
left a comment
There was a problem hiding this comment.
Independent trust/config review — exact head dc690e5d (base b853f15d, stale vs. current dev 22f819ef7)
Verdict: MERGE_READY (approve; rebase is mechanical, no action required on this branch from this lane)
Bug and fix re-verified:
dirs.tsdocuments and implementsGJC_CONFIG_DIR(withPI_CONFIG_DIRlegacy alias) as a dirname joined under home (getConfigDirName()→path.join(os.homedir(), getConfigDirName())viaDirResolver), not a full path.ralplan-runtime.ts,ultragoal-runtime.ts, anddeep-interview-runtime.ts(the settings-file fallback branch, not themodernSettingsPathconfig-dir branch) previously used the raw env value as a full path (process.env.GJC_CONFIG_DIR?.trim() || path.join(os.homedir(), ".gjc")), so setting it per its documented meaning silently pointed the readers at a nonexistent cwd-relative path and fell back to built-in defaults instead of the user's configuredmaxIterations/nudgeBudget/ambiguityThreshold.- Fix replaces all three ad hoc fallbacks with
getConfigRootDir()from@gajae-code/utils, which correctly resolves<home>/<getConfigDirName()>and goes through the same trusted, project-.env-rejectingtrustedConfigDirName()path already used elsewhere (e.g. by PR #3324'sgetConfigDirName()/getAgentDir()usage). ConfirmedgetConfigRootDir()returnsdirs.configRoot, itself built fromgetConfigDirName(). modernSettingsPath()'s first branch (GJC_CODING_AGENT_DIR/PI_CODING_AGENT_DIR→ agent config dir) is untouched by this PR and still reads those two vars directly offprocess.envwithout the project-.envtrust filter. This is pre-existing, out of scope for this PR's stated fix (config-root-as-full-path bug), and not introduced or worsened here — flagging as a known gap for a separate follow-up, not a blocker for this diff.
Precedence verified: getConfigDirName() = trustedConfigDirName("GJC_CONFIG_DIR") ?? trustedConfigDirName("PI_CONFIG_DIR") ?? CONFIG_DIR_NAME, i.e. GJC_ wins over PI_ alias, both individually filtered against project .env, consistent with the sibling trustedAgentDirOverride() pattern.
New tests (config-root-home-relative.test.ts + child-process probe): confirms ralplan/ultragoal read from <home>/<GJC_CONFIG_DIR>/settings.json (not cwd-relative), confirms default .gjc name still used when unset. Correctly spawns a child process since env is read at module load.
Re-verified independently in this session:
git merge-treeagainst current dev (22f819ef7) clean, no conflicts; GitHub reportsmergeStateStatus: CLEAN/MERGEABLE. Stale base is a no-op rebase against the intervening CI-only commit #3322 — rebase recommended before merge for a clean base, no branch mutation performed in this lane.- Exact-head CI (run
30276897880) fully green: 18 pass / 5 skipping, including all four targeted workflow-runtime test files (config-root-home-relative,deep-interview-runtime,ralplan-runtime,ultragoal-runtime). - No automated reviewer (bot) findings posted on this PR.
No blockers found. Approving at exact head dc690e5d1b7279f21ac0b5ca105805f892da6e59.
Footer: Automated trust/config review — PR batch 3324/3327/3328, read-only lane, no merge performed.
dc690e5 to
e91a01d
Compare
Yeachan-Heo
left a comment
There was a problem hiding this comment.
MERGE_READY (verdict reconciliation — exact head)
Reattaching the existing durable verdict to the current exact head e91a01d41cddb3fa7bee53aa1e8b79a3d9d81a40. This is a reconciliation, not a new review: the prior APPROVED was submitted at dc690e5d and the head moved by rebase only.
Evidence:
- File-level shape is identical at both heads:
packages/coding-agent/CHANGELOG.md +1-0,deep-interview-runtime.ts +3-6,ralplan-runtime.ts +2-3,ultragoal-runtime.ts +2-3,test/fixtures/config-root-settings-probe.ts +14-0,test/gjc-runtime/config-root-home-relative.test.ts +69-0. - Normalized patch diff between the two heads is confined to
CHANGELOG.mdcontext lines (neighbouring unreleased entries that landed in between). The PR's own added line is byte-identical, and the three runtime settings readers plus the probe and regression test are unchanged. - Only the parent changed (
b853f15d→d823d9a1). - Exact-head checks: 18 success, 6 skipped, 0 failure.
mergeable_state: clean.
Verdict carries forward unchanged. Merge remains gated on dev returning green (current dev @ fbb56707 is red).
—
[repo owner's gaebal-gajae (clawdbot) 🦞]
c3f060f to
bd1b682
Compare
GJC_CONFIG_DIR is documented as "Config root dirname under home"
(docs/environment-variables.md:477), and dirs.ts implements exactly that:
configRoot = path.join(os.homedir(), getConfigDirName()).
Three workflow settings readers used the value as a full path instead:
ralplan-runtime.ts:244 GJC_CONFIG_DIR?.trim() || join(homedir(), ".gjc")
ultragoal-runtime.ts:493 same
deep-interview-runtime.ts:421 same
deep-interview-runtime.ts:386 join(configRoot, "agent", "config.yml")
So a user following the documentation and setting GJC_CONFIG_DIR=.myconfig had
these look under the *current working directory*, find nothing, and silently
fall back to the built-in defaults.
Measured with a settings file at <home>/.myconfig/settings.json:
dev {"maxIterations":5,"source":"default"}
fixed {"maxIterations":9,"source":"<home>/.myconfig/settings.json"}
getConfigRootDir() also applies the guards these bypassed
(trustedConfigDirName, sanitizeConfigDirName), so a project .env can no longer
choose where workflow settings come from.
The PI_CODING_AGENT_DIR branch in modernSettingsPath is left untouched; only the
config-root branch changes.
Regression drives a child process because the value is read at module load, and
covers ralplan, ultragoal, and the unset case. Reverting fails the two override
cases while the default case keeps passing.
bd1b682 to
ccd82cd
Compare
The same environment variable is interpreted two different ways inside the product.
Documented contract
dirs.tsimplements exactly that:configRoot = path.join(os.homedir(), getConfigDirName()).What these four sites did
They used the value as a full path. So a user who follows the documentation and sets
GJC_CONFIG_DIR=.myconfiggets:The file is not there, so the readers silently fall back to the built-in defaults — no error, no warning, the user's configured value simply never applies.
Measured
Settings file at
<home>/.myconfig/settings.jsoncontaining{"gjc.ralplan.maxIterations": 9},GJC_CONFIG_DIR=.myconfig:Fix
Use
getConfigRootDir(). It is the same computation these performed by hand for the default case, and it additionally applies the guards they bypassed (trustedConfigDirName,sanitizeConfigDirName), so a project.envcan no longer choose where workflow settings come from.The
PI_CODING_AGENT_DIRbranch inmodernSettingsPathis deliberately left untouched —GJC_CODING_AGENT_DIRis documented as a full override, so only the config-root branch is wrong.Tests
config-root-home-relative.test.ts(3 cases) drives a child process because the value is read at module load: ralplan and ultragoal settings resolve under<home>/<GJC_CONFIG_DIR>, and the unset case still resolves under<home>/.gjc.Proof-first: reverting fails the two override cases while the default case keeps passing — so the change is scoped to the documented-override path only.
Verification
bun run checkinpackages/coding-agent: exit 0. New suite 3 pass / 0 fail. ralplan 449 pass, ultragoal 618 pass, deep-interview 672 pass, all 0 fail. Telegram generation guard: clean.