Skip to content

fix(cli): report effective workflow timeouts - #884

Merged
moncher-dev merged 2 commits into
mainfrom
symphony/hojinzs-github-symphony-882
Sep 5, 2026
Merged

fix(cli): report effective workflow timeouts#884
moncher-dev merged 2 commits into
mainfrom
symphony/hojinzs-github-symphony-882

Conversation

@moncher-dev

@moncher-dev moncher-dev commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Issues

Summary

  • TL;DR: make workflow validate report the effective timeout values used by the runtime.
  • Resolve precedence through the shared core helper, expose a stable three-field JSON object, and identify the effective configuration source.
  • Cover explicit runtime settings, legacy codex fallback, parser-materialized runtime defaults, and documented defaults.

Change-point diagram

  • WORKFLOW.md → core parser → resolveWorkflowRuntimeTimeouts → narrowed CLI validation summary → text/JSON report

Start here

  • packages/cli/src/commands/workflow.ts:847 — resolves and narrows effective runtime timeouts for the report.
  • packages/cli/src/commands/workflow.test.ts:125 — pins precedence, all four front-matter shapes, sources, and the JSON key set.

User-Visible Behavior / Operational Impact

  • Text output reports the values the runtime uses under runtime.timeouts.* and identifies runtime.timeouts versus codex/defaults.
  • JSON output moves effective values from the misleading summary.codex.*TimeoutMs fields to summary.runtimeTimeouts.*; no compatibility aliases preserve the incorrect fields.
  • Runtime execution and timeout precedence are unchanged. The separate parser/resolver precedence defect is tracked in fix(core): runtime block without timeouts silently discards legacy codex timeout declarations #888.

Validation

  • pnpm exec prettier --check packages/cli/src/commands/workflow.ts packages/cli/src/commands/workflow.test.ts packages/cli/README.md .changeset/tidy-pandas-report.md — pass
  • pnpm exec vitest run packages/cli/src/commands/workflow.test.ts — pass (23 tests)
  • Repository WORKFLOW.md smoke validation — pass (30000, 900000, 3600000, source runtime.timeouts)
  • pnpm lint — pass
  • pnpm test — pass
  • pnpm typecheck — pass
  • pnpm build — pass
  • Docker E2E — not applicable: CLI reporting only; runtime/integration behavior is unchanged

Changeset

  • .changeset/tidy-pandas-report.md (patch)

Risks & rollback

  • Low risk: the change reuses the runtime resolver and explicitly narrows its result. JSON consumers must migrate to the documented truthful keys; rollback is commits 2a51e85 and fc43c5b.

Changed files

  • packages/cli/src/commands/workflow.ts — report narrowed effective timeouts and their source.
  • packages/cli/src/commands/workflow.test.ts — cover precedence, fallback, defaults, mixed runtime configuration, labels, and JSON shape.
  • README.md — document effective validation output.
  • packages/cli/README.md — document timeout semantics and the JSON key migration.
  • docs/configuration.md — document timeout precedence and worker parity.
  • .changeset/tidy-pandas-report.md — release-note the CLI behavior fix.

Post-merge / human validation

  • None

Security

  • No real tokens, private keys, .env files, or generated installation tokens are committed

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 40.97% 45048 / 109928
🔵 Statements 40.97% 45048 / 109928
🔵 Functions 72.68% 2225 / 3061
🔵 Branches 78.85% 10363 / 13142
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/commands/workflow.ts 80.52% 65.89% 96.77% 80.52% 201-202, 213-220, 231-232, 239-240, 258-259, 265-266, 272-273, 283-284, 290-291, 297-298, 300-301, 314-315, 320-335, 351-352, 420-421, 428-429, 448-449, 453-454, 464-467, 471-472, 477-480, 487-488, 490-493, 496-507, 528-531, 545-548, 598-599, 648-649, 653-656, 669-670, 683-692, 711, 715, 758-759, 763-766, 770-773, 776-779, 807-810, 945-952, 977-980, 994-997, 1010-1023, 1041-1045, 1048-1050, 1053-1057, 1062-1063
Generated in workflow #1404 for commit 2a51e85 by the Vitest Coverage Report Action

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T10:31:49.427003Z fc43c5b Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc43c5bd3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/src/commands/workflow.ts Outdated
Comment thread packages/cli/src/commands/workflow.ts Outdated

@moncher-dev moncher-dev left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@hojinzs ready for your review — verified at head fc43c5bd. This one I exercised for real rather than reading; all three checks pass.

Build

pnpm install --frozen-lockfile && pnpm build && pnpm test in a clean worktree of this head. One failure, core > executeWorkspaceHook > drains large stdout without waiting for the timeout (expected 'timeout' to be 'success') — a load-sensitive timing test on a host at load 21, in a package this PR does not touch (the diff is packages/cli only). Re-run in isolation: passes in 543ms. CI is green on both jobs. Flake, not this change.

packages/cli/src/commands/workflow.test.ts: 22 passed.

1. The reported bug is fixed

Ran the built CLI from both branches against the real ~/moncher-stack/github-symphony/WORKFLOW.md, which declares runtime.timeouts.read_timeout_ms: 30000 / stall_timeout_ms: 900000:

main    codex.read_timeout_ms=5000              <- wrong
        codex.stall_timeout_ms=300000           <- wrong
PR884   runtime.timeouts.read_timeout_ms=30000  <- matches WORKFLOW.md
        runtime.timeouts.stall_timeout_ms=900000

The runtime was always using 30000/900000; only the report was wrong. It now agrees.

2. Legacy shape still reports its own values

Built a fixture with a top-level codex: block only (7777 / 88888 / 999999) and no runtime: block:

main    codex.read_timeout_ms=7777
PR884   runtime.timeouts.read_timeout_ms=7777

Preserved.

3. The label no longer lies about its source

codex.read_timeout_ms= is gone for values that came from runtime.timeouts. The label now names the effective slot.


Two things for you to decide

a) No changeset. CLAUDE.md: "Behavior-changing PRs must add a changeset (.changeset/*.md, package @gh-symphony/cli)." This changes released workflow validate output and adds none — #886 added one for a comparable change. As it stands this fix either rides an unrelated release unattributed, or fails to trigger one. The Codex reviewer flagged the same. Your call given #883 is open.

b) --json schema change. summary.codex.{readTimeoutMs,stallTimeoutMs,turnTimeoutMs} are removed and replaced by summary.runtimeTimeouts.*. Confirmed against --json output. Codex asked for compatibility aliases; I'd argue against them — the old keys held a value the runtime did not use, so a consumer reading a now-missing key fails loudly instead of silently trusting a wrong number. But it is a breaking change to a released command's output and worth being deliberate about.

Separate latent bug found while testing (not this PR's)

resolveWorkflowRuntimeTimeouts returns runtime?.timeouts ?? codex, but the parser materializes runtime.timeouts with defaults whenever a runtime: block is present. So the ?? codex fallback is dead for any workflow that has a runtime: block without a timeouts: sub-block:

front-matter: runtime: {kind: codex-app-server, ...}  +  codex: {read_timeout_ms: 7777, ...}
parsed runtime.timeouts -> {readTimeoutMs: 5000, stallTimeoutMs: 300000}   # defaults
resolved                -> 5000 / 300000                                    # 7777 silently discarded

The orchestrator forwards this resolver, so the runtime also ignores the declared 7777 — the report is right and the behavior is wrong. Only reachable mid-migration (both blocks present), but it fails silently. Filing it separately.

@hojinzs hojinzs left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed at head fc43c5bd. Built the CLI and exercised workflow validate against four front-matter shapes rather than reading the diff alone. All six acceptance criteria of #882 are met, and the reported defect is genuinely fixed — but the new runtimeTimeouts field leaks the whole codex block into --json, so I'm holding on that one line.

Smoke test — packages/cli/dist/index.js workflow validate

shape reported verdict
this repo's WORKFLOW.md (runtime.timeouts declared 30000/900000/3600000) 30000 / 900000 / 3600000 matches the declared values — AC 1, 2 met, the #882 defect is gone
legacy codex: only (7777/88888/999999) 7777 / 88888 / 999999 no regression — AC 3 met
neither block 5000 / 300000 / 3600000 documented defaults — AC 4 met
runtime: without timeouts: + codex: values 5000 / 300000 / 3600000 truthful; see the test-file comment

AC 5 (a test pins precedence) and AC 6 (the label no longer says codex.*) are both satisfied — the it.each block would fail if the report were rewired back to workflow.codex, and not.toContain("codex.read_timeout_ms=") pins the label.

Scope is clean: five files, no speculative refactor, the shared resolver reused rather than re-implemented, and the tempting-but-out-of-scope precedence change correctly left alone. packages/cli/src/commands/workflow.test.ts 22/22 pass; pnpm lint, pnpm typecheck, pnpm build pass; CI green on both jobs. (pnpm format flags 72 files here, all untouched by this PR — a trailing-comma drift in my container, not yours. This PR's five files pass prettier --check.)

Findings

  • P1 — runtimeTimeouts: resolveWorkflowRuntimeTimeouts(workflow) (workflow.ts:885). On the fallback branch the resolver returns the whole workflow.codex object; the declared three-field type doesn't survive to runtime, and --json serializes it verbatim. summary.runtimeTimeouts has 3 keys with a runtime: block and 7 without — including command and approvalPolicy duplicated under a field named "timeouts". Inline comment has the evidence and a one-line fix. This is the only thing blocking.
  • P3 — the label misattributes in the reverse direction for legacy-only files (runtime.timeouts.read_timeout_ms=7777 for a value declared under codex:). Suggested a (source: codex) suffix. Non-blocking.
  • P3 — the mixed shape (row 4 above) has no test. Suggested a fourth it.each entry.

On the two open questions

a) Changeset — I'd add one. Your reading is defensible: the land gate is "changeset present if labeled" (.codex/skills/land/SKILL.md:131) and #882 carries no label, so nothing will block you. But that's the enforcement threshold, not the authoring rule — CLAUDE.md says "Behavior-changing PRs must add a changeset" and CONTRIBUTING.md says "when the change affects released package behavior." This changes released workflow validate output in two ways. With fuzzy-cats-guard.md pending, this fix ships in that release with no changelog line, and the operator who hit #882 has no way to tell which version fixed it. A patch changeset costs one file.

b) --json compatibility aliases — I agree with you, no aliases. The old keys held a value the runtime did not use; a consumer reading a now-missing key fails loudly instead of silently trusting a wrong number, which is the whole point of #882. Codex's concern is real but the alias is the worse resolution. What's missing is that the break is undocumented: all three doc edits describe the text output only, and none of README.md, packages/cli/README.md, or docs/configuration.md mentions that summary.codex.{read,stall,turn}TimeoutMs moved to summary.runtimeTimeouts.*. Put it in the changeset from (a) and a line in packages/cli/README.md. Fixing the P1 first makes that documented schema stable rather than shape-dependent.

The latent core bug you found

Confirmed independently — parser.ts:1143-1151 materializes runtime.timeouts with defaults for any runtime: block, so ?? workflow.codex in resolveWorkflowRuntimeTimeouts is unreachable whenever a runtime: block exists, and a declared codex.read_timeout_ms is silently discarded by the runtime, not just the report. Right call keeping it out of this PR — #882 puts the precedence explicitly out of scope. Please link the follow-up issue here when you file it.

Fix the P1 and take or wave off the two P3s and I'll approve on the next pass.


Generated by Claude Code

Comment thread packages/cli/src/commands/workflow.ts Outdated
Comment thread packages/cli/src/commands/workflow.ts Outdated
Comment thread packages/cli/src/commands/workflow.test.ts
@moncher-dev
moncher-dev requested a review from hojinzs September 5, 2026 11:25

@hojinzs hojinzs left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Approve. Round 2. Every P1/P2 from round 1 is fixed, and I verified each one against the built CLI at 2a51e85 rather than taking the resolution comments at their word.

Acceptance criteria (#882)

# Criterion Verified
1 Reports resolveWorkflowRuntimeTimeouts, not workflow.codex
2 runtime.timeouts declared → reported = declared ✅ this repo's WORKFLOW.md30000 / 900000 / 3600000
3 Legacy codex: only → no regression 7777 / 88888 / 999999
4 Neither → documented defaults 5000 / 300000 / 3600000
5 Test pins precedence, fails on a rewire to workflow.codex ✅ 4-shape it.each, values + source + exact JSON key set
6 Label doesn't misattribute the source ⚠️ letter met, residual noted below

The defect in the issue is gone: the exact command and file that reproduced it now print the declared values.

Smoke test

Built at this head and ran workflow validate across all four front-matter shapes, text and --json:

legacy codex only → 7777/88888/999999   (source: codex/defaults)
runtime.timeouts  → 30000/900000/3600000 (source: runtime.timeouts)
runtime, no timeouts, + codex → 5000/300000/3600000 (source: runtime.timeouts)
neither           → 5000/300000/3600000 (source: codex/defaults)

The round-1 P1 leak is confirmed fixed: summary.runtimeTimeouts is exactly {readTimeoutMs, stallTimeoutMs, turnTimeoutMs} in all four shapes — three keys, never seven — and summary.codex no longer carries timeout keys at all. Grepped the tree for stale references to the removed keys; docs and tests are consistent.

pnpm lint, pnpm typecheck, pnpm build, and packages/cli/src/commands/workflow.test.ts (23 tests) all pass locally. CI green on 2a51e85 (Test, Container Smoke).

Scope

Tight and appropriate. One resolver call, a three-field narrowing, one provenance field, docs, changeset — no over-engineering. The precedence itself is untouched and correctly deferred to #888, and the fourth test case pins today's behavior so that fix has to be a deliberate decision rather than a silent follow.

Non-blocking

Three P3s inline: per-field provenance (codex/defaults can't describe a partially-declared block), the source label being re-derived rather than returned by the resolver, and patch vs minor for the --json key removal. None is worth another round — take them as follow-ups or reply and drop them.


Generated by Claude Code

Comment on lines +933 to +935
runtime.timeouts.read_timeout_ms=${report.summary.runtimeTimeouts.readTimeoutMs} (source: ${report.summary.runtimeTimeoutSource})
runtime.timeouts.stall_timeout_ms=${report.summary.runtimeTimeouts.stallTimeoutMs} (source: ${report.summary.runtimeTimeoutSource})
runtime.timeouts.turn_timeout_ms=${report.summary.runtimeTimeouts.turnTimeoutMs} (source: ${report.summary.runtimeTimeoutSource})

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P3 — codex/defaults conflates two different answers, and per-report granularity can't express a partially-declared block.

The source label is one value for all three lines, so it can only say "not runtime.timeouts". Verified against the built CLI at this head with a codex: block declaring only read_timeout_ms:

codex:
  read_timeout_ms: 7777
runtime.timeouts.read_timeout_ms=7777 (source: codex/defaults)
runtime.timeouts.stall_timeout_ms=300000 (source: codex/defaults)
runtime.timeouts.turn_timeout_ms=3600000 (source: codex/defaults)

7777 came from codex:; 300000 and 3600000 are defaults. Three lines, one label, two genuinely different provenances. The operator who greps WORKFLOW.md for stall_timeout_ms after reading "codex" finds nothing — the smaller version of the loop #882 describes.

This is a real improvement over the previous head and I'm not blocking on it, but the criterion asked for the source to be unambiguous, and per-field provenance is what actually delivers that: resolve each field and label it codex or default individually. Worth a follow-up rather than another round here.

nit, same lines: (source: …) repeated identically three times is noise while the label stays per-report — a single runtime.timeouts source=… line above the three values reads better.


Generated by Claude Code

Comment on lines +892 to +894
runtimeTimeoutSource: workflow.runtime
? "runtime.timeouts"
: "codex/defaults",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P3 — the source is re-derived here instead of coming from the resolver, so it can silently drift when #888 lands.

resolveWorkflowRuntimeTimeouts branches on workflow.runtime?.timeouts; this branches on workflow.runtime. They agree today, and not by luck — WorkflowRuntimeConfig.timeouts is a required field (config.ts:131) and the parser always materializes it (parser.ts:1143-1151), so runtime truthy ⟺ runtime.timeouts present. (Which also means the ?? workflow.codex arm of the resolver is reachable only when runtime is absent entirely — the "runtime block, no timeouts sub-block" shape never falls back, exactly as your fourth test case pins.)

The drift risk is that this is a duplicated invariant rather than a derived one. #888 is about changing precisely that precedence; when it changes the resolver, nothing here fails to compile and no test in this file necessarily fails — the label just starts lying again, in the same class of way #882 was filed for.

Cheap insurance, and it makes the follow-up in the sibling comment fall out for free: have the resolver return the provenance alongside the values (or add a thin resolveWorkflowRuntimeTimeoutSource next to it in core), so the label and the number are read from one place.

Non-blocking — the current output is correct and verified.


Generated by Claude Code

@@ -0,0 +1,5 @@
---
"@gh-symphony/cli": patch

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P3 — patch understates a removal of released --json keys.

I agree with your call on the codex bot's alias request: summary.codex.*TimeoutMs could report values the runtime never used, so aliasing them forward would preserve exactly the failure #882 exists to kill. Removing them is right.

The severity is the separate question that thread didn't cover. summary.codex.{readTimeoutMs,stallTimeoutMs,turnTimeoutMs} were in released workflow validate --json output and are now absent — I confirmed against the built CLI at this head that summary.codex carries only approvalPolicy, threadSandbox, turnSandboxPolicy. Automation reading those keys gets undefined, not a wrong number, which is the better failure but still a break.

A minor bump would put the removal in the changelog where a consumer pinning ~ actually sees it before it bites. The one-line body is otherwise good and does mention the new object.

Your call — if CLI --json output isn't covered by the package's compat surface, say so and this drops.


Generated by Claude Code

@moncher-dev
moncher-dev merged commit f59e24a into main Sep 5, 2026
2 checks passed
@moncher-dev
moncher-dev deleted the symphony/hojinzs-github-symphony-882 branch September 5, 2026 11:51
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.

fix(cli): workflow validate reports codex timeouts the runtime does not use

2 participants