fix(cli): harden init templates per Greptile feedback (suite-wide)#444
Merged
Conversation
Addresses the systemic template issues Greptile flagged across the init template PRs (#434/#435/#436), applied consistently to every affected template instead of one family: - manifest.yaml.j2 (19): render `description` via `{{ description | tojson }}` so a user-supplied description containing YAML-significant characters (`:`, `#`, quotes) can no longer produce an invalid or truncated manifest. - Dockerfile-uv.j2 (19): `agentex init` renders `pyproject.toml` but no `uv.lock`, so `COPY ... uv.lock` + `uv sync --locked` broke a fresh uv build. Drop `uv.lock` from the COPY and `--locked` from `uv sync` so a freshly scaffolded project builds out of the box. - acp.py.j2 / workflow.py.j2 (non-text events): `params(.event).content` is a TaskMessageContent union; reading `.content` on a data/tool message raised AttributeError. Guard with `isinstance(content, TextContent)` before reading the text (async handlers return early, sync handlers return a TextContent notice, streaming handlers end the stream). - default-codex acp.py.j2 (concurrency): serialize turns per task with an asyncio lock so two near-simultaneous events can no longer both read a stale `codex_thread_id` and fork the session. (The temporal-codex variant already serializes via its own turn lock.) Verified by rendering every template: all project Python compiles and every manifest parses with a YAML-hostile description value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the illustrative handler snippets in the sync READMEs to use the same isinstance(content, TextContent) guard as the generated code, so the docs don't teach the crashing params.content.content pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- default-codex: evict the per-task turn lock on cancel so _task_locks does not grow unbounded for the process lifetime. - temporal-langgraph: increment _turn_number after the non-text guard so a skipped non-text event no longer desyncs the turn counter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- default-codex: make per-task lock eviction safe — evict after the turn releases the lock only when it is unlocked and has no waiters, instead of popping on cancel (which could drop a lock a running turn still holds and let a concurrent turn fork the session). - temporal-claude-code, temporal-codex: increment the turn counter after the non-text guard so a skipped non-text event no longer consumes a turn number (matching the temporal-langgraph fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Acquire the per-task lock with try/finally so the lock is released and evicted even if the codex turn raises (previously eviction was skipped on the error path, leaking the lock). - Echo the user message inside the lock so concurrent turns' echoes stay ordered with their turns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What
Applies the systemic template fixes Greptile called out across the init-template stack (#434 / #435 / #436 / #442) suite-wide, instead of in a single template family. Decided as a separate follow-up PR so the stack could merge cleanly first.
Fixes
descriptionin manifests (19 files). Everymanifest.yaml.j2now rendersdescription: {{ description | tojson }}. A user-supplied description containing YAML-significant characters (Answers questions: with Claude,Claude # helper, embedded quotes) could previously produce an invalid or truncatedmanifest.yaml.tojsonemits a valid JSON-quoted scalar, which is valid YAML and handles all cases.uv.lock(19 files).agentex initrenderspyproject.tomlbut never auv.lock, so theCOPY ... uv.lock ./+uv sync --lockedsteps failed the Docker build for a freshly scaffolded uv project. Droppeduv.lockfrom theCOPYand--lockedfrom bothuv syncinvocations so a fresh project builds out of the box.params(.event).contentis aTaskMessageContentunion; reading.contenton a data/tool message raisedAttributeError. Each handler now guards withisinstance(content, TextContent):returnTextContentnoticereturntask/event/sendcalls could both read a stalecodex_thread_idand fork the session. Added a per-taskasyncio.Lockaround the read-modify-write. (Thetemporal-codexvariant already serializes via its own turn lock.)Verification
Rendered every template with a representative context (including a YAML-hostile
description): all generated project Python compiles, and every generatedmanifest.yamlparses with the description value preserved exactly.Not included
README.md.j2doc snippets still show the oldparams.content.contentaccess (illustrative excerpts, not executed). Can follow up if we want docs to mirror the guarded pattern.🤖 Generated with Claude Code
Greptile Summary
uv.lockanduv sync --locked.TextContentguards before reading event text in generated handlers.Confidence Score: 5/5
The template hardening changes are narrowly scoped and align with the described fresh-project initialization failures.
No code issues were identified in the reviewed changes, and the updates consistently address YAML rendering, Docker build assumptions, event content handling, and Codex turn serialization across the template suite.
What T-Rex did
Comments Outside Diff (1)
src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2, line 87-96 (link)The guard runs after
self._turn_number += 1andadk.messages.create(...). When a non-text event arrives, it is still written to task messages and counted as a durable turn before the handler returns. That leaves later Codex turns mis-numbered and records content the handler says it ignored. Check forTextContentbefore incrementing or persisting the message.Artifacts
Repro: generated handler harness for non-text event persistence
Repro: harness output showing message persistence and turn increment before early return
Prompt To Fix With AI
Reviews (4): Last reviewed commit: "fix(cli): make default-codex lock failur..." | Re-trigger Greptile