Anonymous View
Skip to content

fix(cli): harden init templates per Greptile feedback (suite-wide)#444

Merged
declan-scale merged 5 commits into
nextfrom
declan-scale/template-greptile-hardening
Jun 24, 2026
Merged

fix(cli): harden init templates per Greptile feedback (suite-wide)#444
declan-scale merged 5 commits into
nextfrom
declan-scale/template-greptile-hardening

Conversation

@declan-scale

@declan-scale declan-scale commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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

  1. Quote description in manifests (19 files). Every manifest.yaml.j2 now renders description: {{ description | tojson }}. A user-supplied description containing YAML-significant characters (Answers questions: with Claude, Claude # helper, embedded quotes) could previously produce an invalid or truncated manifest.yaml. tojson emits a valid JSON-quoted scalar, which is valid YAML and handles all cases.
  2. Don't require a uv.lock (19 files). agentex init renders pyproject.toml but never a uv.lock, so the COPY ... uv.lock ./ + uv sync --locked steps failed the Docker build for a freshly scaffolded uv project. Dropped uv.lock from the COPY and --locked from both uv sync invocations so a fresh project builds out of the box.
  3. Guard non-text event content (17 handlers). params(.event).content is a TaskMessageContent union; reading .content on a data/tool message raised AttributeError. Each handler now guards with isinstance(content, TextContent):
    • async base handlers: log + early return
    • sync coroutine handlers: return a TextContent notice
    • sync streaming (async-generator) handlers: end the stream
    • temporal workflow handlers: log + early return
  4. Serialize codex turns (default-codex). Two near-simultaneous task/event/send calls could both read a stale codex_thread_id and fork the session. Added a per-task asyncio.Lock around the read-modify-write. (The temporal-codex variant 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 generated manifest.yaml parses with the description value preserved exactly.

Not included

  • README.md.j2 doc snippets still show the old params.content.content access (illustrative excerpts, not executed). Can follow up if we want docs to mirror the guarded pattern.

🤖 Generated with Claude Code

Greptile Summary

  • Hardens CLI init templates across the suite.
  • Quotes manifest descriptions as JSON-compatible YAML scalars.
  • Removes fresh-project Docker build assumptions around uv.lock and uv sync --locked.
  • Adds TextContent guards before reading event text in generated handlers.
  • Serializes default Codex task turns with a per-task lock and updates related README snippets.

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.

T-Rex T-Rex Logs

What T-Rex did

  • Validated the template hardening pass and confirmed the after-state where all 19 manifests parsed with PRESERVED=True and no manifest or Dockerfile failures.
  • Validated the non-text content guard pass and confirmed the after-state results where async, sync, streaming, and temporal checks return the expected values and all templates pass the guard-pattern inspection.
  • Validated the codex turn-lock behavior and confirmed the after-state where A and B synchronize on the per-task lock, session data is stored and resumed correctly, and the harness used for both runs is captured.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2, line 87-96 (link)

    P1 Non-text is persisted

    The guard runs after self._turn_number += 1 and adk.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 for TextContent before incrementing or persisting the message.

    Artifacts

    Repro: generated handler harness for non-text event persistence

    • Contains supporting evidence from the run (text/x-python; charset=utf-8).

    Repro: harness output showing message persistence and turn increment before early return

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2
    Line: 87-96
    
    Comment:
    **Non-text is persisted**
    
    The guard runs after `self._turn_number += 1` and `adk.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 for `TextContent` before incrementing or persisting the message.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Reviews (4): Last reviewed commit: "fix(cli): make default-codex lock failur..." | Re-trigger Greptile

declan-scale and others added 2 commits June 23, 2026 23:05
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>
Comment thread src/agentex/lib/cli/templates/default-codex/project/acp.py.j2
Comment thread src/agentex/lib/cli/templates/sync-codex/project/acp.py.j2
- 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>
Comment thread src/agentex/lib/cli/templates/temporal-claude-code/project/workflow.py.j2 Outdated
Comment thread src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 Outdated
- 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>
Comment thread src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 Outdated
Comment thread src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 Outdated
- 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>
@declan-scale declan-scale merged commit 2d85eb0 into next Jun 24, 2026
48 checks passed
@declan-scale declan-scale deleted the declan-scale/template-greptile-hardening branch June 24, 2026 03:44
@stainless-app stainless-app Bot mentioned this pull request Jun 24, 2026
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.

1 participant