Skip to content

refactor(codex): name child transcripts by role - #8213

Merged
waynesun09 merged 4 commits into
fullsend-ai:mainfrom
dhshah13:refactor/6970-codex-transcript-names
Oct 9, 2026
Merged

waynesun09 merged 4 commits into
fullsend-ai:mainfrom
dhshah13:refactor/6970-codex-transcript-names

Conversation

@dhshah13

@dhshah13 dhshah13 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A child rollout, one whose session_meta names a parent_thread_id, is saved as
<agent>-sub<seq>-<role>.jsonl, the name #6970 gives. <seq> counts the kept child rollouts in
start-time order; <role> is the child's agent_role when it is a valid persona key and generic
otherwise (an unnamed V1 child records a null role). The root keeps <agent>-<basename>. Naming
only, as in #6970.

Inert until PR 6: main registers no roles and keeps the multi-agent tools off (#7829), so no run
produces a child rollout before the switch; every current run's artifacts are byte-equal.

Related Issue

Part of #6970. Supersedes the transcript part of #7809 (closed).

Changes

  • internal/runtime/codex_transcript.go: sort the find listing by rollout basename; decode line
    1 of each validated rollout (codexSessionMeta, codexReadSessionMeta); name a child by its
    role (codexChildTranscriptName, codexGenericRole). Containment, rollout validation and
    redaction are called as before. On review: a rollout whose local name or basename is a
    transcript this extraction already saved is skipped before anything is truncated or
    downloaded over it, and a role the artifact redactor would mask is no role.
  • internal/runtime/codex_transcript_test.go: a per-path fake openshell (the shared fake serves
    one body for every download); cases: named role, null role, invalid role, two children with the
    same role (seq 1 and 2), root unchanged, first line not a session_meta (kept under its
    basename, as today), not a rollout (discarded, as today), a reserved name (default) as
    role; a child is redacted.
  • docs/contributing/runtime-implementation.md was consulted: its Codex transcript rows (rollout
    session JSONL, validated and redacted; transcripts/ holds rollouts) stay true, and the
    user-facing child name lands with the switch PR's Artifacts row.

Interpretations, for a maintainer to correct: <seq> counts kept child rollouts only, 1-based and
unpadded, in ascending basename order (pi uses the dispatch index and keeps a gap for a discarded
child; on Codex the runner sees only the files). A null, empty, malformed or reserved role
(default, which is a config key and never a persona name) or credential-shaped role is
generic. Only the first non-blank line decides identity.

Testing

  • make lint passes (staged first)
  • Tests added: TestCodexChildTranscriptName, TestCodexReadSessionMeta,
    TestCodexExtractTranscripts_NamesChildrenByRole, TestCodexExtractTranscripts_ChildNameRejected,
    TestCodexExtractTranscripts_DownloadLandingNameSaved; the existing transcript tests are unedited.
    Before the ExtractTranscripts change, the new test failed on the names: the four children were
    saved under <agent>-<basename>.
  • go test -race ./internal/runtime/ and make go-test green; go vet clean.
  • Base: origin/main at 0864bdc1; Codex pin CODEX_VERSION=0.159.3.
  • Patch coverage (go tool cover -func): codexChildTranscriptName and codexReadSessionMeta
    100%, ExtractTranscripts 83.5%.

Checklist

  • Title follows Conventional Commits
  • Commits are signed off
  • I wrote this contribution and can explain every change

🤖 Generated with Claude Code

A rollout whose session_meta names a parent thread is a native child's.
It is saved as <agent>-sub<seq>-<role>.jsonl, the name fullsend-ai#6970 gives: seq
counts the kept child rollouts in ascending basename order, role is the
child's agent_role when it is a valid persona key and generic otherwise
(an unnamed child records a null role). The root keeps
<agent>-<basename>. Path containment, rollout validation and redaction
are unchanged. main keeps codex's multi-agent tools off, so no run
produces a child rollout yet.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Name Codex child transcripts by role and sequence

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Name Codex child transcripts by start-time sequence and validated role for consistent artifact
 identification.
• Preserve root transcript names, rollout validation, path containment, and redaction.
• Test child naming, metadata parsing, fallback roles, and unchanged root behavior.
Diagram

graph TD
  A["Codex sessions"] --> B["Sorted rollout paths"] --> C["Validated rollout"] --> D{"Child metadata?"}
  D -->|yes| E["Validated role"] --> F["Contain and redact"] --> G["JSONL artifacts"]
  D -->|no| F
Loading
High-Level Assessment

Naming children during the existing extraction flow is appropriate: it uses the downloaded rollout’s metadata without changing which files are collected or how they are protected. A separate metadata pass or broader extraction redesign would add complexity without a clear benefit.

Files changed (2) +273 / -3

Enhancement (1) +96 / -3
codex_transcript.goName validated child rollouts by sequence and role +96/-3

Name validated child rollouts by sequence and role

• Sorts rollout paths by basename and reads the first non-blank session metadata to identify children. Saved children receive sequential role-based names, with invalid or absent roles mapped to generic; roots retain their basename-based names. Existing rollout validation and redaction remain in the extraction path.

internal/runtime/codex_transcript.go

Tests (1) +177 / -0
codex_transcript_test.goCover Codex child naming and metadata handling +177/-0

Cover Codex child naming and metadata handling

• Adds unit tests for filename formatting and session metadata parsing. A per-path fake download tests out-of-order children, repeated and invalid roles, null roles, unchanged root and non-session-metadata naming, discarded non-rollouts, and child redaction.

internal/runtime/codex_transcript_test.go

@qodo-code-review

qodo-code-review Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Runtime guide review goes unrecorded ✗ Dismissed
Description
CodexRuntime.ExtractTranscripts now names child artifacts from rollout metadata, but the PR
description does not say that docs/contributing/runtime-implementation.md was consulted. Because
CodexRuntime implements the runtime backend and transcript handler, reviewers cannot verify that
the guide’s transcript-extraction guidance was checked for this change.
Code

internal/runtime/codex_transcript.go[R119-125]

+		if meta, metaErr := codexReadSessionMeta(stagePath); metaErr == nil && meta.ParentThreadID != "" {
+			role := codexGenericRole
+			if config.ValidSubagentKey(meta.AgentRole) {
+				role = meta.AgentRole
+			}
+			childName := codexChildTranscriptName(agentLabel, children+1, role)
+			cf, createErr := root.Create(childName)
Evidence
The checklist requires evidence that the runtime implementation guide was consulted when modifying a
runtime backend. The changed lines alter transcript naming; CodexRuntime implements both Runtime and
TranscriptHandler, and the guide identifies transcript extraction as the handler’s responsibility.
The supplied PR description contains no reference to consulting the guide.

Rule 2889480: Consult runtime implementation guide when modifying runtime.Runtime backends
internal/runtime/codex_transcript.go[119-125]
internal/runtime/codex.go[102-104]
docs/contributing/runtime-implementation.md[180-188]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR changes Codex transcript artifact naming without stating that the runtime implementation guide was consulted.

## Fix Focus Areas
- internal/runtime/codex_transcript.go[119-125]

## Recommended Fix
Review docs/contributing/runtime-implementation.md and state in the PR description that it was consulted, including whether its transcript-extraction guidance needs an update.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. A filename collision loses a transcript ✗ Dismissed
Description
ExtractTranscripts can give a child the same local name as a rollout whose basename is
sub<seq>-<role>.jsonl, and root.Create does not reject an existing file. If both rollouts are
present, processing the second removes or replaces the first artifact; even a subsequently discarded
rollout can erase one that was already saved.
Code

internal/runtime/codex_transcript.go[R124-125]

+			childName := codexChildTranscriptName(agentLabel, children+1, role)
+			cf, createErr := root.Create(childName)
Evidence
Root names incorporate the remote basename, while the new child name is generated from the sequence
and role. Session-path validation does not exclude a basename such as sub1-correctness.jsonl, and
rollout validation checks envelope content rather than filename; root.Create and the subsequent
removal do not preserve a file already at that name.

internal/runtime/codex_transcript.go[73-90]
internal/runtime/codex_transcript.go[119-148]
internal/runtime/codex_redact.go[215-263]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Child transcript names can collide with root rollout names, causing an already saved artifact to be removed or overwritten.
## Fix Focus Areas
- internal/runtime/codex_transcript.go[84-90]
- internal/runtime/codex_transcript.go[119-148]
## Recommended Fix
Keep root and child names distinct, or detect collisions without truncating or removing an existing artifact. Add a test with a root basename matching a generated child name.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. A reserved key appears as a child role ✓ Resolved
Description
ExtractTranscripts uses config.ValidSubagentKey alone to accept agent_role, which admits
default even though it is reserved for blanket configuration rather than a persona. A child
rollout carrying that value is saved with a -default.jsonl suffix instead of the generic fallback
used for unusable roles.
Code

internal/runtime/codex_transcript.go[R121-123]

+			if config.ValidSubagentKey(meta.AgentRole) {
+				role = meta.AgentRole
+			}
Evidence
ValidSubagentKey explicitly accepts default, while the configuration code explicitly states that
it is never a legal persona name. The new naming branch applies no further check before putting the
role into the filename.

internal/runtime/codex_transcript.go[119-124]
internal/config/config.go[299-321]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The child-role check accepts `default`, a configuration key that cannot name a persona.
## Fix Focus Areas
- internal/runtime/codex_transcript.go[119-124]
- internal/runtime/codex_transcript_test.go[711-768]
## Recommended Fix
Treat `default` as an unusable child role and use `generic` for it. Add a transcript naming test for `agent_role: "default"`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 89 rules
✅ Skills: 5 invoked
  writing-user-docs
  writing-adrs
  check-patch-coverage
  cutting-releases
  adr-corner
✅ Cross-repo context — repo relationships
  Explored: repo: fullsend-ai/agents (sha: f3dd8897) — View relationship
Review mode: Auto: ⚖️ Balanced: Localized runtime behavior with path safety, parsing, and artifact naming risks.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread internal/runtime/codex_transcript.go
Comment thread internal/runtime/codex_transcript.go
Comment thread internal/runtime/codex_transcript.go Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:22 PM UTC · Completed 10:31 PM UTC

Commit: cd4e7f7 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.05

@dhshah13 dhshah13 added the ok-to-test Allow e2e CI to run after maintainer review (must be re-applied after each push) label Oct 8, 2026
…cript

ValidSubagentKey admits "default", a config key that never names a
persona; the reserved list is what refuses it. A child whose rollout
records a reserved name is now named generic, like one with a null or
malformed role.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.72727% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/runtime/codex_transcript.go 92.72% 3 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Oct 8, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Small two-file PR with balanced test coverage and no protected path changes, elevated to moderate risk by strong historical coupling and recent fix history on the codex runtime components.

Previous run

Risk Assessment: moderate (2/5)

Details

Small two-file PR with balanced test coverage and no protected path changes, elevated to moderate risk by strong historical coupling and recent fix history on the codex runtime components.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Small two-file PR with balanced test coverage and no protected path changes, elevated to moderate risk by strong historical coupling and recent fix history on the codex runtime components.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Small two-file PR with balanced test coverage and no protected path changes, elevated to moderate risk by strong historical coupling and recent fix history on the codex runtime components.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Looks good to me

Earlier findings

  • f_0a52d0b8e397b7fd — resolved_by_change: the skip guard now also checks saved[filepath.Base(remotePath)], covered by TestCodexExtractTranscripts_DownloadLandingNameSaved.
  • f_41015d99990e33cd — resolved_by_change: require.True(config.ValidSubagentKey("sk-a...")) added to the role-naming test.
  • f_b03b4dfcc7d36f44 — reclassified (info): no role-specific redaction bypass found.
Previous run

Review

Findings

Medium

  • [logic-error] internal/runtime/codex_transcript.go:89 — The new saved[localName] guard does not protect the intermediate path sandbox.DownloadFile uses. That helper (internal/sandbox/sandbox.go) removes and downloads to outputDir/<remote basename> and only then renames to the stage path. With agentLabel="triage", a child saved as triage-sub1-correctness.jsonl can be deleted, and overwritten with the raw unredacted download, by a later-sorted agent-writable remote file named triage-sub1-correctness.jsonl. That file passes the saved[localName] check because localName is triage-triage-sub1-correctness.jsonl. If the later file then fails rollout validation, neither transcript survives. The new collision fixture (sub1-correctness.jsonl, unprefixed) never reaches this path.
    Remediation: Download each candidate into an isolated temporary staging directory so DownloadFile's basename-derived intermediate path cannot overlap saved artifacts. Add a regression test with agentLabel="triage" and a later remote basename triage-sub1-correctness.jsonl that fails rollout validation, and assert the original child's contents survive.

Low

  • [missing-test] internal/runtime/codex_transcript_test.go:742 — The credential-shaped role fixture "sk-a..." fails config.ValidSubagentKey (periods), so evaluation short-circuits before redactSummary(role) and the test still passes if the redactor check is removed.
    Remediation: Use a synthetic role that passes persona-key validation and matches the OpenAI pattern, e.g. "sk-" followed by 20 or more lowercase letters. Assert it becomes generic and appears in no output filename.

Earlier findings

  • f_b03b4dfcc7d36f44 — reclassified (low → info): the redactSummary(role) != role check now masks full credential-shaped roles. A below-threshold fragment can still pass, but the rollout body is redacted with the same pattern thresholds, so the filename adds no exposure channel beyond the existing redaction limits.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

Low

  • [secret-exposure] internal/runtime/codex_transcript.go:122 — The child rollout's agent_role is read from an agent-writable file and copied into the artifact filename before codexRedactFile runs. config.ValidSubagentKey and the new reserved-name check (default, explore) restrict shape and length but not content. A role such as glpat- followed by 20 or more lowercase alphanumerics passes both checks and matches the shared gitlab_pat pattern at internal/security/redactor.go:221. Redaction rewrites the file contents only, so that value stays in the saved filename and in the Saved transcript log line. Real tokens with uppercase letters or underscores fail the shape check, which limits exposure. Inputs checked: the role is shape-validated, <seq> is runner-generated, agentLabel comes from the caller, and the combined name gets the os.Root containment check. The role-derived filename is not redacted.
    Remediation: Before using agent_role as the filename role, run it through the shared secret redactor and fall back to generic if redaction changes it. Add regression tests for a valid-shaped credential role and a registered lowercase runtime secret, checking both artifact names and extraction logs.

Earlier findings

  • f_d8ec950aa25d925f — resolved_by_change: the latest commit rejects reserved role names (default, explore) via config.ReservedSubagentKeys() and adds an extraction test case where role default is saved as review-sub5-generic.jsonl.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

Low

  • [logic-error] internal/runtime/codex_transcript.go:125 — Child names (<agent>-sub<N>-<role>.jsonl) share the output namespace with root names (<agent>-<basename>). root.Create truncates and os.Remove(localPath) deletes an existing file of that name, so a rollout whose basename happens to look like a child name (e.g. sub1-correctness.jsonl) and a child can overwrite each other, order-dependent. Real codex rollouts start with rollout-, so this needs an agent-crafted basename in the agent-writable sessions dir; same-basename overwrite across date directories already existed. The new tests use only distinct rollout-* basenames.
    Remediation (optional hardening): track destinations used in this extraction and skip/rename on collision instead of truncating; add a test for both processing orders.

  • [secret-exposure] internal/runtime/codex_transcript.go:122 — A role that passes config.ValidSubagentKey (lowercase alphanumerics and hyphens, ≤64 chars) goes into the filename and the "Saved transcript" stderr line unredacted, so a value like sk- plus 20+ lowercase letters would match the OpenAI-key pattern and appear unmasked. This adds no new channel: the root path's agent-controlled basename is already used unredacted and is wider.
    Remediation (optional hardening): run the role through the shared redactor and fall back to generic if it changes.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Oct 8, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:33 PM UTC · Completed 10:40 PM UTC

Commit: 431af66 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $1.42

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review October 8, 2026 22:40

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot removed the ready-for-merge All reviewers approved — ready to merge label Oct 8, 2026
…ut of new names

A rollout whose name this extraction already saved is skipped before the
reservation would truncate the saved file, whichever of the two was
listed first. A role the artifact redactor would mask is no role, so the
child is named generic like one with a reserved name. A test covers a
child name the output root refuses.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:50 PM UTC · Completed 10:57 PM UTC

Commit: 357c379 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $1.43

fullsend-ai-review[bot]

This comment was marked as outdated.

…s name

DownloadFile lands a file at outputDir/<remote basename> before renaming
it to the staging name, so a later rollout with a saved transcript's
name as its basename would overwrite that transcript on the way in. The
saved-name guard now covers the basename as well. A test with the
triage label and a later triage-sub1-correctness.jsonl that fails
validation shows the child's contents survive.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:09 PM UTC · Completed 11:14 PM UTC

Commit: 751ffaf · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $1.06

@fullsend-ai-review
fullsend-ai-review Bot dismissed stale reviews from themself October 8, 2026 23:14

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Oct 8, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. A Codex review of 751ffaf found nothing at medium or above. The earlier findings are all fixed or answered.

@waynesun09
waynesun09 added this pull request to the merge queue Oct 9, 2026
Merged via the queue into fullsend-ai:main with commit 6a6a06c Oct 9, 2026
83 checks passed

This branch was previously deployed

1 inactive deployment
dev — 751ffaf5 Deployed Oct 8, 2026 by dhshah13 via playback #15904
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Allow e2e CI to run after maintainer review (must be re-applied after each push) ready-for-merge All reviewers approved — ready to merge risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants