Skip to content

fix(cron): suppress stale descendant subagent fallback summaries#44205

Closed
dddabtc wants to merge 3 commits into
openclaw:mainfrom
dddabtc:fix/stale-subagent-status
Closed

fix(cron): suppress stale descendant subagent fallback summaries#44205
dddabtc wants to merge 3 commits into
openclaw:mainfrom
dddabtc:fix/stale-subagent-status

Conversation

@dddabtc

@dddabtc dddabtc commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • stop concatenating multiple descendant fallback replies
  • choose only the freshest settled descendant reply for cron isolated-agent fallback
  • add regression coverage for stale vs fresh descendant replies

Testing

  • pnpm test -- src/cron/isolated-agent/subagent-followup.test.ts

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: M labels Mar 12, 2026
@greptile-apps

greptile-apps Bot commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the cron isolated-agent fallback logic to suppress stale descendant subagent summaries by returning only the freshest settled descendant reply, rather than concatenating up to four older replies that may contain outdated information. It also introduces a dedicated telegram:commands: session lane for bare /status and /stop commands, and adds type assertions in npm-pack-install.ts to satisfy TypeScript's generic narrowing.

Key changes:

  • subagent-followup.ts: Sorting direction flipped to descending, deduplication simplified to first-occurrence-wins on the pre-sorted list, and the multi-reply concatenation loop replaced with an early return on the first non-silent reply.
  • bot-native-commands.ts: New exported resolveTelegramNativeSessionKey helper routes bare /status//stop (optionally @botname-suffixed) to telegram:commands:${identity}, keeping all other slash commands on the default session lane.
  • npm-pack-install.ts: Two as NpmSpecArchiveFinalInstallResult<TResult> casts added to resolve TypeScript compile errors with the complex generic conditional return type.
  • Regression tests added for both readDescendantSubagentFallbackReply and resolveTelegramNativeSessionKey.

One minor style note: the .toSorted() call on latestRuns (line 83–85 of subagent-followup.ts) is redundant since latestByChild.values() is already in descending order due to the upstream sort and first-occurrence-wins map population.

Confidence Score: 4/5

  • This PR is safe to merge; the logic changes are well-scoped and covered by new regression tests.
  • The core behavioral change (return freshest reply only) is correct, the deduplication refactor is logically equivalent to the old implementation, and all four changed files have new or existing test coverage. The only finding is a redundant .toSorted() that has no runtime impact.
  • No files require special attention beyond the minor redundant sort noted in src/cron/isolated-agent/subagent-followup.ts.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/subagent-followup.ts
Line: 83-85

Comment:
**Redundant sort on already-ordered map values**

Since `descendants` is already sorted descending by `endedAt` (line 69), and `latestByChild` is populated by iterating that sorted array with first-occurrence-wins, `latestByChild.values()` is already in descending `endedAt` order (Map preserves insertion order in JS). The `.toSorted()` here is a no-op and can be dropped to avoid misleading future readers into thinking a re-sort is needed.

```suggestion
  const latestRuns = [...latestByChild.values()];
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 999cb72

Comment on lines +83 to +85
const latestRuns = [...latestByChild.values()].toSorted(
(a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Redundant sort on already-ordered map values

Since descendants is already sorted descending by endedAt (line 69), and latestByChild is populated by iterating that sorted array with first-occurrence-wins, latestByChild.values() is already in descending endedAt order (Map preserves insertion order in JS). The .toSorted() here is a no-op and can be dropped to avoid misleading future readers into thinking a re-sort is needed.

Suggested change
const latestRuns = [...latestByChild.values()].toSorted(
(a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0),
);
const latestRuns = [...latestByChild.values()];
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/subagent-followup.ts
Line: 83-85

Comment:
**Redundant sort on already-ordered map values**

Since `descendants` is already sorted descending by `endedAt` (line 69), and `latestByChild` is populated by iterating that sorted array with first-occurrence-wins, `latestByChild.values()` is already in descending `endedAt` order (Map preserves insertion order in JS). The `.toSorted()` here is a no-op and can be dropped to avoid misleading future readers into thinking a re-sort is needed.

```suggestion
  const latestRuns = [...latestByChild.values()];
```

How can I resolve this? If you propose a fix, please make it concise.

@dddabtc
dddabtc force-pushed the fix/stale-subagent-status branch from 999cb72 to dc6b8b0 Compare March 12, 2026 17:03
@openclaw-barnacle openclaw-barnacle Bot added size: S and removed channel: telegram Channel integration: telegram size: M labels Mar 12, 2026

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

Copy link
Copy Markdown
Contributor

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: dc6b8b0087

ℹ️ 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 on lines +78 to 79
if (!childKey || latestByChild.has(childKey)) {
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Preserve newest child run when endedAt timestamps tie

This has(childKey) early-continue keeps the first run seen for a child and drops later ones, which is a regression when two runs share the same endedAt millisecond. listDescendantRunsForRequester preserves insertion order from the registry map (older runs first), and toSorted is stable for equal keys, so equal-endedAt ties now keep the older run and can surface stale fallback text instead of the freshest run. Reintroduce replacement-on-tie behavior (or a secondary tie-breaker like createdAt) so equal timestamps still select the newest record.

Useful? React with 👍 / 👎.

@openclaw-barnacle openclaw-barnacle Bot added the app: macos App: macos label Mar 12, 2026
@dddabtc
dddabtc force-pushed the fix/stale-subagent-status branch from 83fcab8 to dc6b8b0 Compare March 12, 2026 17:49
@openclaw-barnacle openclaw-barnacle Bot removed the app: macos App: macos label Mar 12, 2026
@dddabtc
dddabtc force-pushed the fix/stale-subagent-status branch from dc6b8b0 to 7d2d95a Compare March 12, 2026 18:10

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

Copy link
Copy Markdown
Contributor

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: 7d2d95aa28

ℹ️ 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 on lines +78 to 79
if (!childKey || latestByChild.has(childKey)) {
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Select newest run when endedAt values tie

When two runs for the same child share the same endedAt millisecond, this dedupe path keeps the first entry seen and drops later ones, which can surface stale frozenResultText instead of the most recent result. Fresh evidence in this commit is the new latestByChild.has(childKey) early-continue after a stable toSorted pass, so equal-timestamp ties preserve insertion order from the registry and the newer run never replaces the older one. Please keep replacement-on-tie behavior (or add a secondary sort key like createdAt) so equal endedAt still resolves to the newest run.

Useful? React with 👍 / 👎.

# Conflicts:
#	src/cron/isolated-agent/subagent-followup.test.ts
@dddabtc
dddabtc force-pushed the fix/stale-subagent-status branch from 7d2d95a to 825a8be Compare March 12, 2026 19:07

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

Copy link
Copy Markdown
Contributor

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: 825a8bec60

ℹ️ 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".

// Fallback mode should surface the freshest settled descendant result only.
// Older descendant summaries are easily stale/obsolete and can pollute
// current-status reminders when multiple subagents were used in one workflow.
return reply;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Skip interim text before returning descendant fallback

readDescendantSubagentFallbackReply now returns the first non-NO_REPLY descendant message, but it does not filter out interim acknowledgements like "on it" before return reply. In runs where the freshest descendant ended with an interim status while an older descendant has real output, this function will return the interim text and delivery-dispatch uses it directly as the final delivery payload (src/cron/isolated-agent/delivery-dispatch.ts:325-359), so users can receive placeholder text instead of an actual summary.

Useful? React with 👍 / 👎.

@dddabtc

dddabtc commented Mar 20, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all 3 review comments in 64bfa2c:

  1. Tie-breaking: Added secondary tie-breaker by createdAt when endedAt timestamps are equal, plus a comment explaining why first-occurrence-wins is correct with descending sort.
  2. Redundant sort: Removed the .toSorted() on latestRuns — Map preserves insertion order which already mirrors the descending endedAt sort.
  3. Interim filter: Added isLikelyInterimCronMessage(reply) check in readDescendantSubagentFallbackReply to skip interim acknowledgements ("on it", "working on it", etc.) before returning.

All 25 tests pass.

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

Copy link
Copy Markdown
Contributor

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: 64bfa2cd80

ℹ️ 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 on lines +115 to +118
// Fallback mode should surface the freshest settled descendant result only.
// Older descendant summaries are easily stale/obsolete and can pollute
// current-status reminders when multiple subagents were used in one workflow.
return reply;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Aggregate one usable reply per child before returning

In fan-out cron runs (the code explicitly expects cases like both subagents are running), this helper is the last-resort path when the parent never posts a synthesis. Returning on the first usable entry means src/cron/isolated-agent/delivery-dispatch.ts:325-359 delivers only the newest child's reply and silently drops the other settled children, so a cron job that split complementary work across two subagents can now send a partial answer. The per-child dedupe above is useful, but this fallback should still aggregate the latest usable reply from each distinct child instead of stopping after the first one.

Useful? React with 👍 / 👎.

Comment on lines +112 to +113
if (isLikelyInterimCronMessage(reply)) {
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Restrict interim filtering to actual acknowledgement replies

isLikelyInterimCronMessage is tuned for short cron acknowledgements, but here it is applied to arbitrary child-session output. Any real descendant result under 45 words that happens to contain one of the hint substrings (for example retrying now or should have your summary) gets discarded, and when src/cron/isolated-agent/delivery-dispatch.ts:325-359 asks for a completed descendant reply because the parent text is still on it, that can leave the stale parent message as the only deliverable text. This filter needs to be stricter here than a broad substring match.

Useful? React with 👍 / 👎.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 28, 2026
@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

Summary
The PR changes cron isolated-agent descendant fallback to return the newest usable descendant reply, updates related tests, and adds a generic type assertion in src/shared/global-singleton.ts.

Reproducibility: yes. source inspection gives a high-confidence reproduction path for the PR regression: current main has a focused multi-child aggregation test, while the PR replaces that expectation and returns after the first usable descendant reply.

Next step before merge
The blockers are concrete and localized to cron fallback aggregation/filtering plus a missing changelog entry, so a narrow automated repair is reasonable.

Security
Cleared: The diff is limited to cron fallback code/tests and a process-local generic singleton type assertion, with no workflow, dependency, lockfile, publishing, secret, or downloaded-code changes.

Review findings

  • [P2] Aggregate replies from distinct children before returning — src/cron/isolated-agent/subagent-followup.ts:118
  • [P2] Restrict interim filtering to true acknowledgements — src/cron/isolated-agent/subagent-followup.ts:112-114
  • [P3] Add the required changelog entry — src/cron/isolated-agent/subagent-followup.ts:118
Review details

Best possible solution:

Keep latest-run-per-child selection, aggregate one usable latest reply per distinct child, narrow acknowledgement filtering to true child acknowledgements, and add the required changelog entry.

Do we have a high-confidence way to reproduce the issue?

Yes, source inspection gives a high-confidence reproduction path for the PR regression: current main has a focused multi-child aggregation test, while the PR replaces that expectation and returns after the first usable descendant reply.

Is this the best way to solve the issue?

No. Selecting the newest run per child is useful, but the maintainable fix should preserve distinct-child fan-out aggregation and avoid applying the broad cron interim heuristic to arbitrary child output.

Full review comments:

  • [P2] Aggregate replies from distinct children before returning — src/cron/isolated-agent/subagent-followup.ts:118
    In fan-out cron runs, latestRuns can contain one completed result per child. Returning on the first usable entry delivers only the newest child and silently drops complementary settled child outputs, while current main has regression coverage that joins distinct child replies for this fallback path.
    Confidence: 0.92
  • [P2] Restrict interim filtering to true acknowledgements — src/cron/isolated-agent/subagent-followup.ts:112-114
    isLikelyInterimCronMessage is a broad cron-ack heuristic that matches short text containing hints like retrying now. Applying it to arbitrary child-session results can discard legitimate short descendant output and leave fallback delivery empty or stale.
    Confidence: 0.86
  • [P3] Add the required changelog entry — src/cron/isolated-agent/subagent-followup.ts:118
    This changes isolated cron fallback delivery that users can see, but the branch does not update CHANGELOG.md. Repo policy requires user-facing fixes to add a single-line entry in the active fixes section with contributor credit when known.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.91

Acceptance criteria:

  • pnpm test -- src/cron/isolated-agent/subagent-followup.test.ts
  • pnpm test -- src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts
  • pnpm exec oxfmt --check --threads=1 src/cron/isolated-agent/subagent-followup.ts src/cron/isolated-agent/subagent-followup.test.ts src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts CHANGELOG.md
  • pnpm check:changed

What I checked:

Likely related people:

  • steipete: Current-main blame for the cron fallback helper, delivery path, heuristic helper, and aggregation test points to Peter Steinberger in this checkout; he is the best first maintainer routing candidate for this surface. (role: current maintainer / recent owner; confidence: medium; commits: b37fba7c07a7; files: src/cron/isolated-agent/subagent-followup.ts, src/cron/isolated-agent/delivery-dispatch.ts, src/cron/isolated-agent/subagent-followup-hints.ts)
  • vincentkoc: The current changelog credits @vincentkoc for preserving grouped child-result fallback behavior, which is directly adjacent to the aggregation semantics this PR changes. (role: adjacent behavior owner; confidence: medium; commits: b37fba7c07a7; files: CHANGELOG.md, src/agents/subagent-announce-delivery.test.ts, src/cron/isolated-agent/subagent-followup.ts)
  • Ayaan Zaidi: Local history for the cron/agent area shows several recent isolated-cron delivery and test commits by Ayaan Zaidi, making them a plausible reviewer for delivery semantics if Peter or Vincent want backup. (role: adjacent cron delivery contributor; confidence: low; commits: 13a0d7a9e0, 49ae60d6ca, 31437b9e3b; files: src/cron/isolated-agent)

Remaining risk / open question:

  • No tests were executed in this read-only review; a repair should run the targeted cron tests and changed gate listed below.

Codex review notes: model gpt-5.5, reasoning high; reviewed against a90be474f441.

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 29, 2026
@dddabtc dddabtc closed this by deleting the head repository May 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant