Skip to content

fix(cli): route bare gjc models to --list-models - #12

Open
innocarpe wants to merge 594 commits into
mainfrom
fix/issue-3857-nested-models-chain
Open

fix(cli): route bare gjc models to --list-models#12
innocarpe wants to merge 594 commits into
mainfrom
fix/issue-3857-nested-models-chain

Conversation

@innocarpe

Copy link
Copy Markdown
Owner

Summary

Showcase mirror of upstream Yeachan-Heo#3885. Fixes Yeachan-Heo#3857 (upstream).

Bare gjc models routes to --list-models so nested bash-tool invocations cannot spawn unbounded agent chains.

Yeachan-Heo and others added 30 commits July 30, 2026 18:50
…eo#3480)

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Yeachan-Heo#3559 made a docs-only dev PR select exactly one shard,
`test:packages/coding-agent/test/docs-index-lazy.test.ts`. That key
starts with `test:`, so `taskNeedsNative` reports it as a native
consumer and the matrix entry carries `native: true`. But the plan had
no native producer, so `has_native` was `false`, `affected-native` was
skipped, and the artifact `dev-affected-native-${run_id}` was never
uploaded — while the shard's `if: ${{ matrix.native }}` download step
still fired against it.

The escalation that would have caught this, `ensureNativeBuild`, sits
after the docs-only early return in `planTargetedTasks`, so that path
never reached it; `planTasks` has no such tail at all. Both are fixed at
their own seam.

Yeachan-Heo#3559's own CI stayed green because it edits the test file and
`scripts/`, so it never produced a docs-only plan and never exercised
the path.

Measured on this branch, `CI_DEV_CHANGED_PATHS=docs/guide.md` now
reports `native=true` where dev reports `native=false`.

The merged assertion `toEqual([EMBEDDED_DOCS_GATE_KEY])` was pinning the
defect; it now requires the producer. A separate contract test states the
invariant as the matrix sees it: a docs-only plan must never carry a
native consumer without a producer. The `--native-build is a no-op`
fixture moves off `docs/readme.md`, which now legitimately has a
producer, onto `CHANGELOG.md`, which has none.
)

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…producer

fix(ci): give the docs-only plan the native producer its gate needs
…t and widen provenance to Rust (Yeachan-Heo#3583)

* test: bind viewport evidence to renderer state

* style: format viewport observation types

* docs(tui): record renderer viewport observation and alternate-scroll ownership

* fix: make ascii-no-color viewport evidence host-independent

The ascii-no-color render mode relied on chalk.level = 0, but theme.ts emits
SGR directly through fgAnsi/bgAnsi without consulting chalk. Its colour form
follows detectColorMode(): truecolor when COLORTERM=truecolor, indexed 38;5;N
when TERM is dumb/empty/linux. CI has neither COLORTERM nor a rich TERM, so it
captured indexed colour where a developer host captured truecolor.

Strip ANSI for ascii-no-color artifacts so they are genuinely escape-free on
every host, and derive both the persisted payload and the recorded cursor frame
digest from that one canonical string. Restore the strict escape-free assertion
for that mode; the colour branch keeps the widened check because the previous
regex missed background and bright colour.

* fix: derive viewport capacity oracle from committed paint

The verifier compared transcript_capacity against pin_boundary.row, but tui.ts
assigns both from one renderer local, so that assertion could never fail. It
also dropped the frame-derived capacity derivation and the ordered suffix
marker oracle, leaving 19 of 20 keys bound to renderer self-report.

Derive capacity, status-row index and notice cardinality from the committed
paint, then require the renderer report to AGREE with it, which is strictly
stronger than either source alone. Restore the ordered suffix marker oracle
over the production root order. Add a pin_boundary.row mutation case that is
only detectable against the paint, so the assertion cannot silently become
tautological again.

* fix: address viewport evidence review findings

- capture: replace Awaited<ReturnType<typeof ...>> with a declared
  CaptureProvenance type (AGENTS.md prohibits ReturnType<>)
- tui: reject non-finite setViewportSelection coordinates, matching the
  Number.isFinite guard already in scrollViewportBy. NaN previously
  survived the clamp, latched #mouseSelectionDragged, and forced a
  repaint every frame while reporting a NaN selection
- virtual-terminal: delete unused getCursorPosition(). Measured against
  all 20 fixture keys it agrees with the reported cursor in only 5;
  the other 15 sit at (rows-1, columns) where the final frame write
  left the emulator cursor, so it cannot serve as a cursor oracle
- CHANGELOG: record the reported-cursor-column clamp, and scope the
  wheel-scrollback claim to the capture-off case

* style: collapse capture signature to one line

* fix: canonicalize every persisted ascii-no-color viewport frame

Required metadata.json artifacts carried raw ANSI through captureFrame(),
so the visible-empty IRC frame and all nine resize probes embedded whichever
colour form the capture host negotiated (indexed 38;5;n under TERM=dumb,
truecolor 38;2;r;g;b otherwise). The earlier canonicalAnsi strip covered only
the top-level terminal-ansi.txt and the cursor digest, leaving the required
metadata host-dependent while both verifiers reported success.

- captureFrame() now canonicalizes at the single point every persisted frame
  flows through, deriving text and sha256 from that canonical value.
- The verifier rejects any CSI/ESC byte in every required ascii-no-color frame
  field, not just the top-level payload.
- semantic_anchor.id embedded a per-run entry id, which made the bundle
  irreproducible even within one host; it is now a deterministic digest of the
  anchor's own geometry.
- Adds two-environment reproducibility coverage and a rehashed metadata
  corruption case that reaches the new guard.
- Removes the orphan hardware-cursor doc line left by the deleted accessor.

* docs(tui): move unreleased changelog entries out of the released 0.12.1 section

The PR's Added/Changed/Fixed bullets were nested under the already-released
[0.12.1] heading, leaving [Unreleased] empty. AGENTS.md requires new entries
under [Unreleased] only. Every heading from [0.12.2] down is restored
byte-identical to upstream; the change is 12 insertions, 0 deletions.

* fix(test): bind semantic anchor identity to a domain-separated digest and drop repo-derived status text

The persisted `semantic_anchor.id` was the only field anchoring the painted
row, but it hashed geometry alone and truncated to 8 hex. That made it both
heavily aliased (distinct entries painting different content at equal offsets
collapsed onto one id) and brute-forceable, and the verifier only checked that
it was a nonempty string.

The id is now the full domain-separated SHA-256 of the entry key, namespace,
painted row text, complete geometry including frameRow, and the committed frame
digest. Every preimage input is persisted, and the verifier recomputes the
expected id from those inputs plus the committed paint before any downstream
metadata check, rejecting arbitrary, transplanted, aliased, malformed, and
truncated ids as well as content mutation under unchanged geometry.

Required evidence frames also embedded the capture host's repository state:
`#getGitStatus()` returns its cache and fires the fetch asynchronously, so one
paint rendered `detached` and a later one `detached +8` in the same worktree.
The fixture now pins the status line to model/session_name segments, so no
repo-derived text reaches required metadata at all.

* fix(test): scope viewport provenance digest to its declared surface

The provenance digest hashed `git diff --binary HEAD --` over the entire
worktree, and the verifier recomputes it live. That coupled bundle validity to
every tracked file in the repo, so an unrelated edit anywhere retroactively
marked already-captured bundles stale — and any write landing inside the
capture-to-verify window flipped the digest mid-run, masking whichever guard was
actually under test. Narrow the digest to a declared scope and persist
`git_diff_scope` so the covered surface is read off the bundle rather than
inferred, with the verifier comparing it against its own constant.

Proven behaviorally: an out-of-scope edit is accepted, an in-scope edit is still
rejected.

* docs(tui): keep unreleased changelog entries out of released sections

* fix(test): derive semantic anchor row and geometry from an immutable source expectation

The persisted semantic_anchor.id was a full domain-separated digest, but the
verifier recomputed it from producer-supplied fields. A producer that mints the
bundle therefore mints a cryptographically valid id for any chosen row and
geometry: transplanting geometry from another entry and relocating the anchor one
painted row down, then recomputing every digest, manifest, review-input binding,
and scoped provenance, was accepted. Digest consistency proves internal
consistency, not semantic authenticity.

The verifier now carries a frozen per-entry expectation for frame row and
complete grapheme/cell geometry. That table is authoritative because the
verifier's own bytes are inside source_sha256, so a bundle producer cannot
rewrite it without invalidating provenance. Measured across both color
environments, the geometry is identical for all 17 anchors, so the table is a
host-independent invariant rather than a captured artifact.

The id preimage bound the host-negotiated ANSI frame digest, which made 16 of 17
ids differ between indexed-color and truecolor hosts even though the stripped
semantic paint was byte-identical. Semantic identity now binds the stripped-text
digest; the ANSI digest stays persisted as an artifact binding and is still
checked against the committed frame.

* fix(test): anchor oracle expectation integrity to the committed blob

The semantic-anchor expectation table is only immutable if its digest comes
from outside the bundle's reach. captureProvenance() hashes the WORKTREE
file, so mutating the table and restamping provenance was accepted: the
stamp described the mutation instead of rejecting it.

Compare every oracle source against its committed blob at the manifest's
own git_head. A bundle author cannot restamp that without pushing a commit,
which changes git_head. Fails closed when the blob is unreadable, and runs
before semantic-anchor validation so a mutated table cannot ride through.

* test(tui): reject a restamped oracle-expectation mutation

* fix(test): bind oracle integrity to any reachable committed blob and widen provenance scope to Rust crates

* fix(test): bind oracle integrity to any reachable commit and widen provenance scope to Rust

* test(tui): derive the synthetic-merge base from dev instead of local topology

* fix(test): make oracle integrity authority a single exact commit

The previous gate fell back to reachableBlobSha256s() over --all, so oracle
bytes committed on ANY local or remote-tracking ref satisfied it. Reachability
is not review authority: an unrelated attacker ref could authorize bytes that
were never the reviewed head, and in CI the fetch topology silently became a
security input.

Authority is now exactly one commit: GJC_STICKY_VIEWPORT_ORACLE_COMMIT when
set, otherwise provenance.git_head. It is validated as a full commit object,
both oracle sources must resolve at that same commit, the chosen commit is
persisted as provenance.oracle_commit and cross-checked at verify, and there is
no ref fallback.

* fix(test): supply git identity to the oracle ref-topology regression

---------

Co-authored-by: twoimo <twoimo@twoimoui-MacBookPro.local>
Co-authored-by: twoimo <twoimo@users.noreply.github.com>
…eo#3582)

The exact cancellation-ownership merge made deferred agent_end publication await background persistence and the user extension queue. That tied public session readiness to extension latency and regressed the same message-pipeline shard on two consecutive dev heads. Start persistence and extension delivery in the established background order, but retain the exact producer lease until the queued extension event settles.

Constraint: Public agent_end must settle before user extension handlers while session shutdown still drains queued delivery and exact cancellation ownership remains live through that delivery.
Rejected: Restore the old immediate lease close | loses the cancellation-domain lifetime introduced by the ownership fix.
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not await user extension delivery from public terminal publication; retain its producer lease through queue settlement instead.
Tested: 181 focused coding-agent/agent tests passed with 852 assertions; coding-agent Biome and TypeScript checks; git diff --check.
Not-tested: Full hosted Dev CI and install-method matrix.
Resolve sticky-viewport provenance and oracle source reads from one canonical repository root, and replace the post-merge-invalid authority topology assumption with an explicit deterministic older commit.
Bind ANSI-stripped text for all 20 reviewed sticky-viewport frames to the exact oracle commit, including null-anchor capacity-zero cases and coordinated non-anchor forgery regressions.
…eak (Yeachan-Heo#3605)

Five ultragoal test suites (critic-gate, dogfood, review,
durable-completion-release, runtime) created their temp checkpoints inside
the enclosing git work tree. computeCheckpointChangeSet merges the CI
planner's CI_DEV_CHANGED_PATHS into the computed change set, so on any
branch that touches a computer control surface path (tools/index.ts,
settings-schema.ts, computer.ts, pi-natives/computer/) the generic gate
fixtures falsely triggered the mandatory computer red-team suite and
failed with COMPUTER_REDTEAM_CASE_MISSING: ... must include
kill-switch-bypass.

The production kill-switch-bypass gate is correct and unchanged. The
fixtures simply did not isolate their own contract from the host branch's
diff. ultragoal-runtime.test.ts already solved this for its validation-
batch tests via batchTempDir() (outside git + pinned env); the other
suites never applied the same hermetic pattern.

Each affected suite now captures CI_DEV_CHANGED_PATHS once at module
load, clears it in beforeEach, and restores the original in afterAll.
The explicit CI-leak tests that set their own value (computer-red-team-
fixtures, review branch-merge) continue to work because beforeEach clears
the baseline before each test and their own try/finally restores within
scope.

Added a positive regression: a complete mandatory computer red-team gate
(all seven adversarialCases incl. kill-switch-bypass) PASSES on a
genuine computer change -- proving the gate is not weakened.

Lore-id: 3533redteam
Constraint: must not weaken, bypass, or suppress the kill-switch-bypass gate
Tested: all 1211 gjc-runtime tests pass with CI_DEV_CHANGED_PATHS pinned to a computer surface path
Not-tested: full dev-ci shard matrix (run on push)
Confidence: high
Scope-risk: narrow
Reversibility: trivial

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…tinuation dispatch (Yeachan-Heo#3607)

* fix(gjc-runtime): make psmux authority and continuation dispatch host-independent

Restacks the Windows psmux authority work onto current dev, dropping the
storage lineage dev already absorbed via squash, and repairs every
darwin-only failure in the slice.

Five separate platform sources decided psmux behavior independently; four
ignored the authority test seam entirely, so Windows-authority paths were
unreachable on POSIX and eleven tmux-sessions tests failed there:
- psmuxAuthorityEnvironments gated persisted-authority discovery on raw
  process.platform, aborting before any later guard could run.
- resolveGjcTmuxProviderContext and createGjcTmuxSession each recomputed
  the platform instead of consulting the seam.
- teamProviderAuthority read process.platform directly; GjcTeamConfig now
  carries the platform so startGjcTeam's value reaches it.

Continuation dispatch had two real defects: the ack-loop deadline used
raw Date.now() while every other timestamp in the file routes through the
seamed clock, and the reservation site threw where the revalidation site
records a skipped outcome, so one worker's short lease aborted an entire
monitor pass.

tmux-sessions: 35/35 on darwin (was 24/35). team-runtime: 115/117, with
zero psmux-only failures - both remaining names also fail at dev, and
this slice fixes three that dev fails.

* test: align session-command untagged diagnostic with the psmux authority message

* fix(lifecycle): keep POSIX on dev's tmux lifecycle path

psmux rewrote the darwin lifecycle spawn/cleanup path (completeNonLinuxLifecycleSpawn,
cleanupDirectLifecycleAttempt), which regressed POSIX: dev is 60 pass / 0 fail on
notifications-lifecycle-control-runtime, psmux was 51 / 9. psmux is a Windows-only
provider and POSIX uses tmux, so the darwin path must stay dev's.

Reverting the file to dev keeps every psmux suite green (tmux-sessions 35/0,
psmux-detect 39/0, launch-tmux 137/0, tmux-gc 9/0) and preserves the win32 lifecycle
content, which dev already carries from the earlier merged psmux-authority slices.

* fix(test): restore POSIX lifecycle and session metadata round-trip fidelity

* fix(gjc-runtime): unblock psmux authority publish after dev reverted the managed sync lock

* fix(gjc-runtime): tolerate non-claim task version bumps during continuation revalidation

* Revert "fix(gjc-runtime): tolerate non-claim task version bumps during continuation revalidation"

This reverts commit ebc3ca196801cd6b50b726c51d22ed3ba704e9ce.

* Reapply "fix(gjc-runtime): tolerate non-claim task version bumps during continuation revalidation"

This reverts commit 56f68822097d04d335126c7542f19e9dd720748e.

* fix(gjc-runtime): enforce psmux authority migration exclusion without the managed sync lock

The Windows psmux authority publish previously acquired `acquireManagedLockSync`
from managed-session storage. Dev removed that symbol when it reverted Yeachan-Heo#3489, so
the import broke on merge and the whole slice failed at module load.

Reviewers asked for the sync lock to return only alongside a real consumer and a
shared async/sync release contract, which belongs to the resident storage branch
rather than here. So this slice now enforces the property it actually needs on its
own: a retained lock descriptor for the exact authority name blocks publication
unless its owner process is definitely gone. Expiry alone is not permission,
because a paused writer with an expired lease is still live and stealing the name
would let two writers publish one generation-scoped authority.

`publishManagedFileNoReplaceSync` already supplies create-without-clobber
exclusion for the payload itself, so no lease helper is required.

* fix(gjc-runtime): fence continuation dispatch and restore the split marker

Repairs the two blockers the reviewer identified on the previous head.

1. A public authority-changing operation dispatched no continuation argv.
   The continuation reservation is written under the task lock, that lock is
   released, and only then does dispatch revalidate eligibility. A concurrent
   claim-releasing operation could land in that window, so revalidation observed
   no current claim and the incident was journalled skipped with nothing
   dispatched. Revalidation plus send-keys now runs inside the same team
   mutation fence every other public authority-changing operation takes, and the
   fence is released before the ACK wait because the ACK is published by a
   separate receiver process that must acquire the same cross-process fence.
   Worker GC prune now takes that fence too, since it deletes claim records.

2. Replacement settled before pane-launch ACK. The fake tmux rewrite dropped the
   three-field tmux-last-split marker its shell predecessor published on every
   split. The memory-guard replacement test waits on that marker before
   publishing the generation-bound startup ACK, so the ACK never arrived,
   relaunch hit its startup deadline, and the guard reported retrying. The
   marker is restored. The same test also aborted its marker waiter before its
   own advisory-host early return, which masked the advisory outcome on
   non-Linux hosts; the ordering is fixed.

* fix(gjc-runtime): keep pane id narrowing in the fenced dispatch closure

The fence-hold restructure moved the send-keys argv construction into a
closure, which dropped the outer non-null narrowing of `worker.pane_id`.
Assert it the same way the adjacent `executeTeamTmuxMutation` calls already
do, so the frozen argv stays `readonly string[]`.

* fix(gjc-runtime): bind a proven pane id for fenced continuation dispatch

---------

Co-authored-by: twoimo <twoimo@twoimoui-MacBookPro.local>
Co-authored-by: gjc <gjc@local>
Retry-hint inspection tees response bodies, so a response abandoned for another attempt can retain buffered data. Begin cancellation only after every caller-return path is excluded, and do not await transport-controlled cleanup that could stall retry or abort progress.

Constraint: Returned responses must remain unconsumed, including final attempts and over-cap retry hints.
Rejected: Await body cancellation before retry | a custom or stalled transport can leave the cancellation promise pending indefinitely.
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep discarded-body cleanup non-blocking and after all response-return decisions.
Tested: 8 focused fetch-retry tests including pending/rejecting cancellation and abort; packages/utils Biome and typecheck; git diff --check
Not-tested: Live network socket reuse and transport-specific cancellation behavior
Proxy event conversion already rejects type-mismatched delta and end events except for toolcall_end. Failing closed at the same conversion boundary prevents a malformed proxy sequence from being silently accepted as a successful terminal message while preserving valid tool-call finalization.

Constraint: Preserve the existing proxy event and valid tool-call output contract.
Rejected: Ignore the malformed event and rely on a later terminal | accepts incomplete provider state without diagnostics.
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep proxy content-finalization events fail-closed when their content index has the wrong type.
Tested: bun test packages/agent/test; bun --cwd=packages/agent run check; git diff --check
Not-tested: Live remote proxy transport
…ent shard-red captureIncomplete (Yeachan-Heo#3611)

The prior fix (Yeachan-Heo#3605) cleared CI_DEV_CHANGED_PATHS in beforeEach but
left temp dirs inside the enclosing git work tree. Under parallel
shard load (bun test --shard=N/8), git commands inside
computeCheckpointChangeSet timeout at their 5s budget, causing
captureIncomplete=true which unconditionally triggers the mandatory
computer red-team suite -- even when no computer surface path is
present in the change set.

Root cause: captureIncomplete=true is a conservative production safety
default, but it fires falsely in test environments under parallel
load because the git diff/ls-files commands race the timeout.

Fix: relocate all five affected suites' tempDir() to os.tmpdir()
(outside the git work tree). When the cwd is not inside a git work
tree AND ciChangedPaths is non-empty, computeCheckpointChangeSet
returns { paths: ciChangedPaths } without captureIncomplete. Pin
CI_DEV_CHANGED_PATHS to a non-computer test path so the returned
paths don't trigger the suite.

This mirrors the existing batchTempDir() pattern from
ultragoal-runtime.test.ts which has been stable since its
introduction. Also adds .tmp-* to packages/coding-agent/.gitignore
to prevent any remaining in-repo test artifacts from polluting
untracked-file inventory.

The production kill-switch-bypass gate (MANDATORY_COMPUTER_CASE_IDS,
requiresComputerRedTeamSuite, validateMandatoryComputerAdversarialCases)
is byte-identical to dev. No enforcement is weakened.

Lore-id: 3533shard5
Constraint: must not weaken genuine computer red-team enforcement
Tested: shard 5/8 with exact CI plan paths -> 0 COMPUTER_REDTEAM failures
Not-tested: full 8-shard matrix (run on push)
Confidence: high
Scope-risk: narrow
Reversibility: trivial

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…dedup (Yeachan-Heo#3613) (Yeachan-Heo#3615)

recordUltragoalReviewBlockers created unbounded review_blocker goal chains
and duplicated goals for identical objectives, unlike its sibling
recordReviewFindingGoals which already deduped via findOpenReviewBlockerGoal.

Independently reproduced on dev 29ddfe0: identical objectives produced
duplicate goals (2 calls -> 2 goals); distinct findings grew without bound
(8 calls -> 8 goals, no cap); no user-reachable safe stop existed.

Fix (3 parts, all in ultragoal-runtime.ts):
1. Exact-objective dedup reusing findOpenReviewBlockerGoal, scoped to the
   same blockedGoalId. On dedup-hit: idempotent return of the matched
   existing goal id, no writePlan, no appendLedger.
2. MAX_REVIEW_BLOCKER_DESCENTS=3 cap counting unresolved descents (status
   not in {complete,superseded}) off the blocked goal. 4th attempt throws
   typed UltragoalReviewBlockerRecursionCapError BEFORE any mutation
   (read-check-then-write on the persisted plan snapshot). CLI surfaces it
   as exit 1 with the typed marker. Dedup applied before budget; same-root
   budget durable across replay/restart/concurrency.
3. CLI record-review-blockers returns the matched existing id on
   dedup-hit, new id on creation (was blindly goals.at(-1)).

9 direct regressions added: identical-objective dedup, CLI receipt
truthfulness, cap+typed-error+no-partial-mutation, CLI cap surfacing,
resolved-ancestor exclusion, restart/replay durability, missing-goal
handling, ordinary single-round preservation, cross-goal non-dedup.

All 198 tests pass (181 existing + 17 nudge-guard + 9 new). TS check clean.

Signed maintainer admission: the unbounded review_blocker growth defect in
recordUltragoalReviewBlockers is independently confirmed on dev 29ddfe0 via
repository-owned reproduction. Evidence is structural unboundedness + identical-
commit repro only; no private token totals or third-party session counts are
validated as repository fact. The disputed attribution figure is retracted.

Lore-id: 3613-ultragoal-recursion-cap
Constraint: MAX_REVIEW_BLOCKER_DESCENTS must default to 3 (descents 1..3 ok, 4th triggers terminal handoff)
Constraint: dedup before budget check; same-root budget durable across replay/restart/concurrency
Constraint: fail closed without corrupting goals.json/ledger
Constraint: preserve ordinary single-round and sibling recordReviewFindingGoals behavior
Rejected: dedup only no cap | distinct findings still unbounded, no safe stop
Rejected: cap only no dedup | duplicates still corrupt identity/ledger
Confidence: high
Scope-risk: narrow
Reversibility: trivial
Tested: 198 tests (181 existing + 17 nudge-guard + 9 new regressions)
Not-tested: cross-process file-locking (out of scope, single-process TOCTOU closed)

Closes Yeachan-Heo#3613

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Adds the official bundled OpenAI Codex LunaMaxxing profile with exact role mappings and catalog coverage.
Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Align historical issue dispositions and monitor documentation with the current product surface. Coalesce persistent monitor notifications and preserve local URL authority decoding safeguards.
Review lanes could self-declare matching hashes even after the worktree advanced. Runtime-owned cohorts now derive canonical source identity before dispatch, persist lane provenance, classify late delivery as stale, and require current delivery receipts at the completion gate.

Lore-id: issue-3469

Constraint: preserve one issue per branch and PR

Rejected: equal reviewer-declared hashes | stale lanes can agree on the wrong source

Confidence: high

Scope-risk: medium

Reversibility: revertable

Tested: package check; 236 focused tests; definition and rebrand gates

Not-tested: full workspace check blocked by pre-existing stale generated docs index on current dev
The source snapshot coordinator was implemented and documented but omitted from the canonical workflow manifest, so static skill-doc verification rejected the public command. Register the verb and typed arguments, expose help, and add black-box manifest coverage.

Lore-id: issue-3469-ci-repair

Constraint: do not absorb the dev docs-index regeneration owned by PR Yeachan-Heo#3630

Confidence: high

Scope-risk: low

Reversibility: revertable

Tested: coding-agent check; 238 focused tests; workflow manifest and skill-doc gates
Hostile review found bypasses around legacy hash-only gates, partial dispatch binding, nested persisted records, post-delivery source changes, and concurrent cohort writes. Require runtime cohort authority, bind every dispatch field, validate nested provenance, revalidate source at join, reject unsupported isolated review execution, sanitize rerun output, and retry CAS conflicts.

Lore-id: issue-3469-review-fixes

Confidence: high

Scope-risk: medium

Reversibility: revertable

Tested: coding-agent check; source/receipt/command/default focused suites; manifest and skill-doc gates
Fresh hostile review found stale classification could be fooled by reverting owner bytes and that delivery identity remained ambiguous. Bind deliveries to opaque dispatch/cohort IDs, classify against the active cohort, dedupe replay, reject ambiguous persisted authority, reuse unchanged freezes, and bind every declared task field.

Lore-id: issue-3469-final-review-fixes

Confidence: high

Scope-risk: medium

Reversibility: revertable

Tested: coding-agent check; focused source/receipt/command/default suites
The first fail-closed gate applied runtime cohort authority to every completion, breaking ordinary checkpoints and replay flows. Preserve existing non-source-aware cohort semantics while requiring runtime authority whenever persisted cohorts, cohort IDs, or delivery IDs assert source-aware review evidence.

Lore-id: issue-3469-runtime-repair

Confidence: high

Scope-risk: medium

Reversibility: revertable

Tested: complete ultragoal runtime suite; source/command/task receipt suites; coding-agent check; manifest and skill-doc gates
…n-Heo#3632)

`bc11b36ca` edited `docs/sdk-rpc-parity-audit.md` and `docs/tools/monitor.md`
without regenerating the embedded docs index, so `check:public-sync` and
`docs-index-lazy.test.ts` fail on dev and on every PR based on it.

Regenerated with the repo's own generator; no hand edits.

Co-authored-by: GJC <gjc@example.test>
The current PR head changes the public workflow surface and public-sync deterministically requires the canonical embedded docs index generated from the exact base plus PR tree.

Lore-id: issue-3469-public-sync

Generated: bun run generate-docs-index

Tested: bun run check:public-sync
Yeachan-Heo and others added 27 commits August 5, 2026 14:30
… preset

The docs-index.generated.ts embeds a hash of models.md; adding the new
qwen-deepseek profile preset changed the models.md length, so the
embedded hash went stale and failed the check:public-sync gate.

Lore-id: 8f2e1c9a
Confidence: high
Scope-risk: low
Reversibility: revert-profile
Tested: check:public-sync
Not-tested: full test suite (worktree has no node_modules)
The affected-path check:@gajae-code/ai gate failed on a formatting
violation: Biome prefers the models.find(...) call on a single line.
Collapse it to satisfy the formatter.

Lore-id: 8f2e1c9a
Confidence: high
Scope-risk: low
Reversibility: revert-profile
Tested: biome check packages/ai/scripts/generate-models.ts
)

fix(memory): defer startup until model profiles settle
fix(ai): honor MiniMax M3 official 1M routes
…presets (Yeachan-Heo#3827)

feat(alibaba-token-plan): add qwen-deepseek profile preset
…puter suite (Yeachan-Heo#3767)

The SKILL says the computer-use red-team suite is "conditional, not
universal" and tells the agent to pick the surface that matches what the
change actually ships. The runtime is stricter than that: since Yeachan-Heo#3543 it
decides applicability from the computed change set and fails closed, so
any edit to a shared behavior registry demands the suite even when the
diff contains nothing computer-related.

Following the doc as written leads an agent to conclude the suite is
skippable, then hit COMPUTER_REDTEAM_CASE_MISSING at
`checkpoint --status complete` with no explanation of why -- and the
tempting way out is to invent the seven mandatory cases, which is
exactly what the gate exists to prevent.

Record the real rule instead: the suite is required for computer source,
the computer tool, the three shared behavior registries
(`config/settings-schema.ts`, `tools/index.ts`, `tools/renderers.ts`),
and any incomplete change-set capture; generated bindings, prompt/skill
docs and everything else do not trigger it on their own. Also state the
sanctioned way out -- supply a genuine suite or escalate for an
authorized override -- so the failure mode has a documented exit that is
not fabrication.

Path claims verified against `categorizeComputerChangePath` and
`isComputerControlSurfaceCategory` rather than transcribed by hand.

Docs only; no runtime behavior change.

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…xact unlink (Yeachan-Heo#3834)

* fix(notifications): resolve intermediate notifications dir only for exact unlink

Successor to closed Yeachan-Heo#3832. Multi-account layouts that share notifications/
via a directory symlink still need intermediate reparse points resolved
before native exact unlink can retire transition locks. Full-path realpath
followed final components and could race a final-component symlink swap
past native AT_SYMLINK_NOFOLLOW.

Canonicalize only the parent directory and rejoin the original basename so
native mutation still no-follows the final path component. Bump
DAEMON_GENERATION 49→50 and refresh the generation guard manifest for the
protected exactUnlinkNotificationFile change.

Lore-id: 3761act02
Constraint: final-component file symlink must remain reparse_point under TOCTOU
Constraint: do not claim full Yeachan-Heo#3761 closure -- bounded activation slice only
Rejected: full-file realpathSync | weakens final-component no-follow under race
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: issue-3761-symlinked-notifications-activation; generation-50 pin; generation-guard suite; coding-agent check
Not-tested: live Linux multi-account Telegram bot inbound round-trip

Refs Yeachan-Heo#3761

* fix(sdk): lock registerToolSessionTransitionCleanup out of public inventory

Yeachan-Heo#3813 added AgentSession.registerToolSessionTransitionCleanup for shared
artifact-manager ownership on session transitions. The method is an internal
lifecycle registration seam, parallel to registerToolSessionCleanup, but was
never added to LOCKED_EXCLUSIONS. Exact-head Dev CI then fails the generated
SDK operation inventory check on any PR based on current dev.

Classify the seam as a locked exclusion and regenerate the committed matrix.

Lore-id: 3813seam01
Constraint: do not expose as a public SDK control
Rejected: map to a new SDK operation | no user-facing control exists
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: sdk-operation-inventory.test.ts (17 pass); inventory --check
Not-tested: full coding-agent shard matrix on CI

Refs Yeachan-Heo#3813
…eo#3840)

A Darwin crash during exact replacement cleanup can leave an empty exchange placeholder at the canonical receipt name, causing every later append to fail closed. Reconcile that state only when the receipt quarantine identity proves the placeholder is stale, while continuing to reject substituted non-empty receipts. Also migrate version-one receipts left by interrupted upgrades.

Lore-id: 6f4a2c91
Constraint: never delete a canonical replacement receipt unless its detached identity is proven
Constraint: preserve live transcripts and quarantined receipt payloads during recovery
Rejected: trust the canonical placeholder by filename | an attacker or concurrent writer could substitute a live entry
Confidence: high
Scope-risk: focused
Reversibility: simple
Tested: Darwin native receipt recovery, legacy v1 receipt migration, managed replacement regressions, typecheck, Biome
Not-tested: concurrent multi-process replacement stress
…eachan-Heo#3831)

Configured legacy retry.* could re-issue a turn after partial assistant
text, thinking, or tool-call content had already crossed the public stream
boundary (e.g. proxy response.failed / upstream_stream_error). That path
only applied replay-safety checks to first-event timeouts and bare
defaults, so unclassified failures still entered bounded unknown retry.

Apply one universal automatic gate: once the failed attempt carries
observable assistant content, non-managed auto-retry returns false.
Content-free clean failures keep existing bounded/unbounded policy;
managed provisional discard, credential rotation, first-event scope
checks, and manual /retry remain unchanged.

Lore-id: 3791a1b2
Constraint: must not invent a parallel retry classifier -- extend existing content/replay helpers
Constraint: managed provisional discard and content-free credential rotation must remain retryable
Rejected: terminal special-case for upstream_stream_error only | same bug class under any code/message
Rejected: blanket hasCleanRetryReplaySafety for all legacy retries | over-blocks mid-turn after prior tool results
Confidence: high
Scope-risk: narrow
Reversibility: easy
Directive: do not auto-continue committed partial responses as retry
Tested: agent-session-resilient-retry (Yeachan-Heo#3791 cases) + retry-fallback + manual-retry + coding-agent check
Not-tested: live Responses proxy partial-then-failed e2e; extension-only current-attempt effects without content
Supersedes: none

Fixes Yeachan-Heo#3791
…nses/Azure setup (Yeachan-Heo#3829)

* fix(ai): delegate lazy stream watchdogs to transports

The lazy provider wrapper watched normalized assistant events while several providers already watched richer raw transport events. That second clock could expire after transport-only progress, replace a live response with a blank generic stall error, and race provider-specific failure handling.

Providers with raw watchdogs now own timeout decisions; the shared wrapper remains for providers that need it. OpenAI Completions now honors caller idle overrides internally, Azure shares the semantic Responses progress filter, and provider-owned paths preserve caller cancellation.

Lore-id: e4a32f9c
Constraint: providers without raw transport watchdogs retain the shared lazy watchdog
Rejected: raise the global timeout | masks watchdog ownership and delays genuine stalls
Confidence: high
Scope-risk: medium
Reversibility: code-only
Tested: packages/ai check; 2153 package tests and 10224 assertions; 115 timeout and concurrency tests
Not-tested: live provider outage recovery

* fix(ai): bound Responses/Azure setup to first-event timeout

Delegating lazy-stream watchdogs to transports removed the outer
first-event clock, but Responses and Azure only armed their idle
iterator after create() returned. A never-resolving pre-headers fetch
could then wait the SDK default (10 minutes) before any provider
watchdog existed.

Map streamFirstEventTimeoutMs into the OpenAI/Azure SDK request timeout
via a shared helper (Completions parity), and normalize pre-connect
Azure SDK timeouts to typed stream_first_event_timeout. Keep
transport-owned idle progress after the stream arms.

Lore-id: b7c4e19a
Constraint: keep provider-owned raw-event idle after create() returns
Constraint: Completions-style explicit-vs-fallback SDK timeout rules
Rejected: re-enable outer first-event for all provider-owned paths | dual clocks race transport progress
Confidence: high
Scope-risk: medium
Reversibility: code-only
Tested: packages/ai check; openai-first-event-timeout, register-builtins, stream-timeout-defaults
Not-tested: live Azure/Responses hung-header outage recovery
Slack's conversations.replies endpoint rejects JSON request bodies with
invalid_arguments even though equivalent form-encoded requests succeed.
Serialize all Slack Web API POST parameters with URLSearchParams, omit
undefined fields, and pin the request contract with a regression test.

Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: Slack provider and daemon 60/60; coding-agent check; CLI smoke

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
PR Yeachan-Heo#3806 flattened time-dependent loaders to 80 ms everywhere, making the working shimmer visibly step on direct terminals. Restore the 16 ms color cadence only for direct local sessions while keeping SSH and multiplexers on the bounded cadence and retaining congestion-based frame drops.

Lore-id: tui-local-shimmer-cadence
Constraint: SSH and multiplexed terminals must retain 80 ms decorative churn bounds
Rejected: revert Yeachan-Heo#3806 and Yeachan-Heo#3814 | restores slow-terminal stale-frame backlogs
Confidence: high
Scope-risk: narrow
Reversibility: clean-revert
Tested: loader cadence and scheduler congestion tests; TUI and coding-agent package checks
Not-tested: visual comparison across physical terminal emulators
Restores the 16 ms time-dependent loader cadence on direct local terminals while retaining 80 ms transport bounds and congestion dropping for SSH and multiplexers.
Successor to closed PR Yeachan-Heo#3700, rebased on current dev.

- Shared durable Telegram topic authority with generation-CAS convergence, durable pre-create claims, lease-fenced effects, and host-qualified file locks (owner_host_id) that fail closed for foreign hosts.
- Requires ok === false, error_code === 400, and an exact allowlisted Telegram description before treating an archive error as idempotently settled; identical TOPIC_NOT_FOUND text under 401/403/429/500 remains archive_pending with a durable bounded retry job.
- Remote archive closes daemon-created topics (closeForumTopic) without deleting retained records; user-created topics are never closed/removed.
- Crash-atomic topic registry persistence (fsync file+directory, native Windows exact write-through replacement); versionless legacy state is quarantined, never interpreted as empty.
- Isolated owner-backed validation-supergroup mode for bot-API testing that persists nothing.
- Generation 51 / serving epoch 5; exact-generation ownership compatibility (fail-closed).
`gjc update` fetched `https://registry.npmjs.org/@gajae-code/coding-agent/latest`
directly, and so did the interactive startup version check. The install that
follows shells out to bun/npm and therefore already honors whatever registry the
user configured, so on any network that mirrors or blocks the public registry the
two halves disagreed: the check died with `Failed to fetch release info:` — empty,
because an intercepting proxy returns a status with no statusText — while the
install it was gating would have succeeded.

Resolve the registry before the request, the way npm does, and attach the
credentials registered for it by walking the registry path up one segment at a
time like npm's nerf darts. A configured-but-unusable registry now throws instead
of quietly falling back to the public one, which would reintroduce the bug.

Lore-id: 7c3f9a1e
Constraint: the version check must reach the same registry the install shells out to
Constraint: a repository the user merely cloned must not choose the host this process talks to
Rejected: read <cwd>/.npmrc like npm does | a hostile repo could name the destination and supply an ${ENV}-expanded token to send there; npm skips project config in global mode anyway, and this check gates a global install
Rejected: read process.env directly | Bun merges cwd/.env into it, so the same repo-controlled path returns through the environment; $credentialEnv is the boundary this repo already established for exactly that
Rejected: shell out to `npm config get registry` | adds a process spawn to interactive startup
Rejected: fall back to registry.npmjs.org when the configured mirror fails | converts a deterministic misconfiguration into an intermittent one and leaks the request to a host the user excluded
Directive: do not promote a userinfo-only registry URL to a credential — `https://trusted.example.com@attacker.example.com` is that shape
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test packages/coding-agent/test/npm-registry.test.ts
Tested: bun test packages/coding-agent/test/update-cli.test.ts packages/coding-agent/test/startup-update-contract.test.ts
Tested: bun run check:tools and tsc -p packages/coding-agent/tsconfig.json --noEmit
Tested: live resolution against a corporate network that returns HTTP 503 for registry.npmjs.org, reaching the Nexus mirror named in the user .npmrc
Not-tested: an authenticated mirror end to end; credential attachment is covered only by unit tests

Co-authored-by: Claude <noreply@anthropic.com>
* feat(slack): adopt existing threads before readiness

Add an opt-in prepare, bind, and activate lifecycle for daemon-owned Slack thread adoption. Fence binding and activation on exact live authority, corroborate durable mappings, and drop control frames before chat mutation.

Co-authored-by: gaebal-gajae <gaebal-gajae@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>

* fix(slack): fence cancellation, activation, and binding authority

Owner review of Yeachan-Heo#3816 found six blocking gaps where a failure or an
unvalidated input was allowed to fall through instead of stopping.

Cancellation: the cleanup unlinked the request and the response claim while
suppressing both errors. If only the request unlink failed the orphaned
request stayed servable, so a submission already answered `cancelled` could
be re-claimed and committed. The claim is now released only once the request
is proven gone; a leaked response object is inert, releasing it is not.

Activation: deferred readiness withheld `session_ready` but left the control
dispatcher reachable, making activation optional for input admission.
`control_request` is now refused while prepared. `session_activate` is a
separate frame type, so activation itself is unaffected.

Binding authority: the proof was built from one endpoint read and the
connection made from a second, and neither derived the state-root scope nor
compared `endpointMtimeMs`. A replacement process repeating the endpoint
generation could be connected to, and a `.gjc/state/chat/sdk` session was
read at the wrong scope. Authority now derives scope exactly as the runtime's
attach() fence does, verifies the endpoint mtime the index observed, and
carries the proven endpoint so activation never re-resolves.

Preparation input: `prepare_existing_thread` was compared with `=== true`
against unvalidated dispatch arguments, so the string "true" silently became
false and started an ordinary session that accepted the prompt. A non-boolean
is now rejected before any mutation.

Compensation: a failed defensive `session.close` was swallowed and the
resulting error sealed under the idempotency key, leaving a live untracked
session while retries replayed the cached failure. The failure now names the
session and is marked nonterminal so the key stays open.

Shutdown: `#started` stayed true across the unregister await, so an
activation resolving during teardown could publish readiness afterwards.
Teardown now fences before its first await.

Lore-id: 4b91c7d2
Constraint: activation must remain reachable while controls are withheld
Constraint: a cancelled submission must never commit later
Rejected: gate query_request and register_provider too | read-only and setup paths, blocking them risks breaking legitimate pre-activation clients without closing an admission gap
Rejected: keep the second endpoint read and compare pid again | the generation and pid can both repeat across a restart; only the mtime-proven endpoint is unambiguous
Confidence: high
Scope-risk: moderate
Reversibility: easy
Tested: bun test sdk-session-readiness-lifecycle sdk-slack-thread-binding sdk-chat-daemon-control-frames notify-thread-commands coordinator-mcp-server (128 pass, 0 fail)
Tested: bun --cwd=packages/coding-agent run check
Not-tested: prepare_existing_thread rejection and the compensation nonterminal path have no dedicated case; the coordinator harness needs setup this change did not add

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: gaebal-gajae <gaebal-gajae@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
…acklog (Yeachan-Heo#3848)

The issues/ directory read as 21 open items while README already recorded
01-08, 14-18, 20-21 as resolved and 11-13, 19 as obsolete (stdio RPC retired).
Spot-checks against current dev re-confirmed the fixes (credential-import
root guards, web-search local-baseUrl guard, session.resumeModelBehavior).
Resolved/obsolete files move to issues/archive/ for provenance; only the
deferred architectural pair 09/10 stays top-level.

Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: manual spot-checks of fixes 14, 17, 21 against source
Not-tested: full re-verification of every archived item

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Boundary-review advisory from the ultragoal backlog sweep: the doc pointed at
issues/01-issues/13, which now live under issues/archive/ (and 09/10 were never
archived). Correct the path range and regenerate the embedded docs index.

Confidence: high
Scope-risk: narrow
Reversibility: clean-revert
Tested: docs-index regeneration; grep confirms no remaining stale issues/0N live-path references
Cohort report (cleaner/architect/qa joined lanes), terminal-critic verdict,
PTY verification capture, and replayExempt record for the ultragoal
backlog-resolution run's completion gate. Evidence for commits 35b0b4c
and 959632f.

Confidence: high
Scope-risk: narrow
Reversibility: clean-revert
Tested: gjc ultragoal quality-gate validate passes against these artifacts
Yeachan-Heo#3851) (Yeachan-Heo#3854)

* fix(session): graceful fallback for oversized (~64 MiB) session resume (Yeachan-Heo#3851)

Resuming a session whose transcript file is at or above the ~64 MiB
managed-storage per-file bound no longer OOMs, stalls the process, or
fails with a bare unhandled rejection. The resume/open path now performs
a stat-based size pre-check before the full read/decode/parse path and
fails closed with a structured `oversized` reason
(SessionTranscriptOversizedError) plus recovery guidance.

The bound matches the existing MANAGED_ARTIFACT_MAX_FILE_BYTES
(64 * 1024 * 1024) and is not raised; sub-limit sessions resume
unchanged. The pre-check fires in inspectResumeSessionFile (covering
--resume picker, --continue, and auto-resume) and in the explicit-path
branch of SessionManager.open (covering --resume <path>).

Lore-id: a1b2c3d4
Constraint: must not raise the on-disk managed-storage per-file limit
Constraint: must not break sub-limit session resume or missing-file create-fresh behavior
Rejected: silently raise MANAGED_ARTIFACT_MAX_FILE_BYTES | masks OOM risk; does not solve in-session growth
Rejected: lazy/streaming transcript parse | large scope change to identity hash + schema validation contract
Confidence: high
Scope-risk: moderate
Reversibility: trivial
Tested: oversized resume fail-closed (inspect, openExistingStrict, SessionManager.open)
Tested: sub-limit session resume unchanged
Tested: operator-facing error message redaction (bare-resume + normal startup)
Not-tested: native Linux managed-storage read_managed content_too_large path (existing coverage unchanged)

Closes Yeachan-Heo#3851

* style(session): biome formatter/import hygiene for Yeachan-Heo#3851 oversized resume

Fixes five Biome-only errors blocking CI on PR Yeachan-Heo#3854:
- Sort imports in session-manager.ts (MANAGED_ARTIFACT_MAX_FILE_BYTES)
- Sort imports in main.ts (SessionTranscriptOversizedError)
- Sort imports in resume-confirm-continue.test.ts
- Fix indentation in session-manager.ts explicit/inspect resume guards
- Remove unused TempDir import from session-manager-resume-oversized.test.ts

No behavioral changes. All 61 focused resume tests pass.

Lore-id: a1b2c3d4
Confidence: high
Scope-risk: trivial
Reversibility: trivial
Tested: bun --cwd=packages/coding-agent run check (biome + tsc clean)
Tested: 61 resume-focused tests pass (oversized + readonly + confirm-continue)

---------

Co-authored-by: gaebal-gajae <gaebal-gajae@users.noreply.github.com>
The reference client wrote capability negotiation failures through console.warn, which bypassed centralized logging and made embedding unsafe for the TUI. Route bounded diagnostics through the shared logger and expose an immutable optional callback without allowing callback failures to stop message handling.

Lore-id: c58e17e1
Constraint: diagnostics must exclude transport secrets, identifiers, raw frames, and user content
Rejected: injectable logger sink | duplicates the centralized logging policy and lets embedders suppress the default record
Confidence: high
Scope-risk: narrow
Reversibility: clean
Tested: Telegram reference and notification e2e suites; coding-agent package check
…and loader tests (Yeachan-Heo#3865)

The G010 animation-scheduler red-team tests assumed timeDependentColor loaders always share the 80ms cadence bucket, but Yeachan-Heo#3845 routes them to a 16ms bucket on direct local terminals; no dev CI lane runs the tui suite, so the stale expectations surfaced as the 0.12.12 main-push shard failure. Review then found the same host-dependence in loader.test.ts: its env helper omitted TERM, which the multiplexer predicate treats as tmux/screen when so prefixed.

Pin the terminal transport context (including TERM) in both files, add a DIRECT-LOCAL test asserting the 16ms/80ms split, and keep the SSH/TMUX constrained-terminal tests on explicit signals.

Lore-id: a9f3c102
Constraint: must follow existing terminal-env stubbing conventions from loader.test.ts and animation-scheduler.test.ts
Rejected: revert resolveAnimationCadence to 80ms-only | the 60fps direct-local gradient is intended shipped behavior (Yeachan-Heo#3845)
Rejected: environment-conditional assertions | nondeterministic across CI runners and developer shells
Confidence: high
Scope-risk: narrow
Reversibility: clean-revert
Tested: full packages/tui suite 1068/0 across 76 files; adversarial host envs (SSH, TMUX, cleared, TERM=tmux-256color, TERM=screen-256color) 8/8 each on both files; combined sibling invocation 18/18; biome + tsc clean; Dev CI green
Not-tested: windows CI runner timing (suite runs under fake timers)
…tion (Yeachan-Heo#3867)

The 600-table fixture wrote one CREATE TABLE per autocommit, so fixture
creation cost was a per-statement fsync storm (~4.6-6.8s under CI disk
load vs ~830ms idle). That blows bun's 5s per-test timeout; the timeout
then lets afterEach rm the temp dir while the read lifecycle is still in
flight, so the pending read surfaces a spurious `Path ... not found`
ToolError. Batching the schema writes into a single BEGIN/COMMIT keeps
all 600 tables byte-identical while collapsing the fsync cost to one
commit, making the test deterministic within the budget without
touching the timeout or weakening the direction assertions.

Confidence: high
Scope-risk: narrow
Reversibility: clean-revert
Tested: focused test 25x under concurrent fsync load (was 0/x passing)
Tested: full read-direction file 10x under load (19/19)
Tested: read suite (read-goldens, read-receipt, read-stream-collector, read-artifact-spill, read-artifact-tree-authorization, read-acp-fs, read-multi-range, read-summary, read-tool-group) 232 tests
Tested: tools/sqlite.test.ts 33 tests
Tested: bun --cwd=packages/coding-agent run check (biome + tsc)
Not-tested: CI shard re-run (no merge/CI rerun per task)

Co-authored-by: gaebal-gajae <gaebal-gajae@users.noreply.github.com>
Agents often invoke `gjc models` from the bash tool expecting a catalog.
Without a route, `models` was a free-form launch prompt, so nested agents
re-ran the same command and spawned an unbounded process chain.

Rewrite the mistaken subcommand to the existing non-agent --list-models
path so the invocation exits after a bounded listing.

Lore-id: 3857models
Constraint: must not start an interactive agent for bare `models`
Constraint: managed child/session launch paths unchanged
Rejected: nested-session hard refuse | broader than this regression; breaks intentional one-shot launches from tool env
Rejected: dedicated models subcommand registration | --list-models already owns listing semantics
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: routeModelsAlias unit cases; nested GJC_SESSION_ID spawn; no leftover grandchild process
Not-tested: full interactive agent recursion tree under load
Fixes: Yeachan-Heo#3857
@innocarpe innocarpe added the bug Something isn't working label Aug 5, 2026
CI check:@gajae-code/coding-agent failed solely because biome wanted the
multi-arg routeModelsAlias expectation on one line.

Lore-id: 3857fmt01
Constraint: no behavior change -- formatter only
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: cli-command-surface + issue-3857-nested-models-chain
innocarpe pushed a commit that referenced this pull request Aug 6, 2026
…3894)

An aborted run could wait forever in Promise.allSettled when a tool ignored its AbortSignal. Emit an aborted tool result for unresolved calls so the agent turn can terminate.

Issue: #12

Tested: bun test packages/agent/test/agent-loop.test.ts

Tested: bun --cwd=packages/agent run check
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid nested gjc models invocation can recursively spawn an unbounded agent chain