Skip to content

fix(coding-agent): resolve workflow settings under the config root - #3327

Merged
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
10kH:fix/config-root-home-relative
Jul 28, 2026
Merged

fix(coding-agent): resolve workflow settings under the config root#3327
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
10kH:fix/config-root-home-relative

Conversation

@10kH

@10kH 10kH commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

The same environment variable is interpreted two different ways inside the product.

Documented contract

| `GJC_CONFIG_DIR` | Config root dirname under home (default `.gjc`) |

dirs.ts implements exactly that: configRoot = path.join(os.homedir(), getConfigDirName()).

What these four sites did

ralplan-runtime.ts:244         GJC_CONFIG_DIR?.trim() || path.join(os.homedir(), ".gjc")
ultragoal-runtime.ts:493       same
deep-interview-runtime.ts:421  same
deep-interview-runtime.ts:386  path.join(configRoot, "agent", "config.yml")

They used the value as a full path. So a user who follows the documentation and sets GJC_CONFIG_DIR=.myconfig gets:

documented / dirs.ts :  <home>/.myconfig
these readers        :  <cwd>/.myconfig

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.json containing {"gjc.ralplan.maxIterations": 9}, GJC_CONFIG_DIR=.myconfig:

dev    {"maxIterations":5,"source":"default"}
fixed  {"maxIterations":9,"source":"<home>/.myconfig/settings.json"}

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 .env can no longer choose where workflow settings come from.

The PI_CODING_AGENT_DIR branch in modernSettingsPath is deliberately left untouchedGJC_CODING_AGENT_DIR is 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 check in packages/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.

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts documents and implements GJC_CONFIG_DIR (with PI_CONFIG_DIR legacy alias) as a dirname joined under home (getConfigDirName()path.join(os.homedir(), getConfigDirName()) via DirResolver), not a full path. ralplan-runtime.ts, ultragoal-runtime.ts, and deep-interview-runtime.ts (the settings-file fallback branch, not the modernSettingsPath config-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 configured maxIterations/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-rejecting trustedConfigDirName() path already used elsewhere (e.g. by PR #3324's getConfigDirName()/getAgentDir() usage). Confirmed getConfigRootDir() returns dirs.configRoot, itself built from getConfigDirName().
  • 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 off process.env without the project-.env trust 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-tree against current dev (22f819ef7) clean, no conflicts; GitHub reports mergeStateStatus: CLEAN/MERGEABLE. Stale base is a no-op rebase against the intervening CI-only commit #3322rebase 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.

@10kH
10kH force-pushed the fix/config-root-home-relative branch from dc690e5 to e91a01d Compare July 27, 2026 22:06

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md context 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 (b853f15dd823d9a1).
  • 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) 🦞]

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/config-root-home-relative branch 2 times, most recently from c3f060f to bd1b682 Compare July 28, 2026 03:02
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.
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/config-root-home-relative branch from bd1b682 to ccd82cd Compare July 28, 2026 03:24
@Yeachan-Heo
Yeachan-Heo merged commit 0835276 into Yeachan-Heo:dev Jul 28, 2026
25 checks passed
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.

2 participants