Skip to content

fix(flows): shorter approval TTL for copilot live-run parks - #5112

Merged
graycyrus merged 2 commits into
tinyhumansai:mainfrom
graycyrus:fix/flows-copilot-approval-ttl
Jul 22, 2026
Merged

graycyrus merged 2 commits into
tinyhumansai:mainfrom
graycyrus:fix/flows-copilot-approval-ttl

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

flows_build's streaming (copilot pane) path already scopes APPROVAL_CHAT_CONTEXT + AgentTurnOrigin::WebChat (PR #5090) so run_flow / resume_flow_run park for a real human approval instead of auto-allowing. Those parks were using the gate's full 10-minute DEFAULT_APPROVAL_TTL (src/openhuman/approval/gate.rs:65) — a long time to leave a live Slack/Gmail/HTTP node waiting on a copilot pane the user may have already navigated away from.

This reuses the existing "shorten a specific park" mechanism already established for live meetings (IN_CALL_APPROVAL_TTL / APPROVAL_IN_CALL_CONTEXT, issue #3513) rather than adding a parallel path:

  • New COPILOT_APPROVAL_TTL constant (180s / 3 min) in src/openhuman/approval/gate.rs.
  • New APPROVAL_COPILOT_STREAM_CONTEXT task-local, scoped by flows::ops::flows_build's streaming branch alongside the existing APPROVAL_CHAT_CONTEXT scope (src/openhuman/flows/ops.rs).
  • The gate reads the new task-local exactly like it already reads APPROVAL_IN_CALL_CONTEXT, and clamps the park window through a new ApprovalGate::resolve_park_ttl helper — extracted from the existing in-call min/clamp logic so the clamp-selection is unit testable without actually parking a future and waiting out a multi-minute TTL.
  • DEFAULT_APPROVAL_TTL itself is untouched, and no new global constant replaces it.

Only flows_build's streaming branch scopes the new context, so main-chat and every other approval path (external channel, in-call meetings, workflow automation, CLI, composio's own park_bound) keep their existing behavior unmodified.

Test plan

  • GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml — passes.
  • GGML_NATIVE=OFF cargo test --lib --manifest-path Cargo.toml approval::gate — 47 passed (including 5 new resolve_park_ttl_tests covering: default park unclamped, copilot clamp shortens a 10-minute default, in-call clamp unaffected by the new flag, a clamp never extends an already-shorter boot-time TTL, and the tighter-of-two-clamps case).
  • GGML_NATIVE=OFF cargo test --lib --manifest-path Cargo.toml openhuman::flows::ops — 183 passed, no regressions.
  • cargo fmt --check on changed files — clean.

Summary by CodeRabbit

  • Bug Fixes
    • Approval requests generated during copilot streaming now expire based on a dedicated, shorter TTL (about 3 minutes).
    • Approval TTL clamping now applies correctly across copilot-stream, in-call, and default scenarios, without extending the gate’s own effective TTL—even with debug overrides.
    • Stored approval expiry (expires_at) is now aligned with the actual clamped wait duration to reduce stale waits.
    • Added targeted test coverage for TTL resolution and copilot-stream persistence behavior.

flows_build's streaming (copilot pane) path already scopes
APPROVAL_CHAT_CONTEXT + AgentTurnOrigin::WebChat so run_flow /
resume_flow_run park for a real approval instead of auto-allowing
(PR tinyhumansai#5090). Those parks were using the gate's full 10-minute
DEFAULT_APPROVAL_TTL, which is a long time to leave a live
Slack/Gmail/HTTP node waiting on a copilot pane the user may have
already navigated away from.

Add a new APPROVAL_COPILOT_STREAM_CONTEXT task-local, scoped by
flows_build's streaming branch alongside the existing chat context,
and a COPILOT_APPROVAL_TTL (180s) constant. The gate reads the new
task-local the same way it already reads APPROVAL_IN_CALL_CONTEXT for
live meetings, and clamps the park window through a new
resolve_park_ttl helper (extracted from the existing in-call min/clamp
logic so it's unit testable without parking + waiting out a real TTL).

Only flows_build's streaming branch scopes the new context, so
main-chat and every other approval path keep the unmodified 10-minute
default.
@graycyrus
graycyrus requested a review from a team July 22, 2026 08:45
@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea930496-aca4-4148-a6b0-a319be168cdf

📥 Commits

Reviewing files that changed from the base of the PR and between 48f0c78 and 1f1487a.

📒 Files selected for processing (1)
  • src/openhuman/approval/gate.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openhuman/approval/gate.rs

📝 Walkthrough

Walkthrough

ApprovalGate now shortens parked approval TTLs for Flow Canvas copilot streaming runs. A task-local context is exported and applied around streaming execution, with persisted expiry calculation and tests covering default, in-call, copilot, shortened, and combined TTL cases.

Changes

Copilot approval TTL

Layer / File(s) Summary
Approval TTL resolution
src/openhuman/approval/gate.rs
Adds copilot-specific context and shared TTL clamp resolution, applies the resolved duration to persisted expiry and waiting behavior, and tests supported TTL combinations.
Streaming context wiring
src/openhuman/approval/mod.rs, src/openhuman/flows/ops.rs
Re-exports the copilot context and scopes it around the flows_build streaming agent run.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FlowCanvasCopilot
  participant flows_build
  participant ApprovalGate
  FlowCanvasCopilot->>flows_build: Start streaming run
  flows_build->>ApprovalGate: Scope copilot streaming context
  ApprovalGate->>ApprovalGate: Resolve clamped park TTL
  ApprovalGate->>ApprovalGate: Persist matching expiry
Loading

Possibly related PRs

Suggested labels: bug, rust-core

Suggested reviewers: m3ga-mind

Poem

A rabbit hops through streams so bright,
And trims approval’s waiting night.
A tiny flag, a shorter stay,
Keeps copilot hops on track today.
Carrot cheers for gates that flow! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: shortening approval TTL for copilot streaming/live-run parks in flows.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@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: 48f0c780fa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/approval/gate.rs Outdated
Comment on lines +986 to +987
let effective_ttl =
Self::resolve_park_ttl(self.effective_ttl(), in_call_ctx.is_some(), copilot_stream);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist the copilot TTL on approval rows

When a copilot approval is parked, the wait is clamped here, but the PendingApproval inserted above still uses expires_at = now + self.effective_ttl() (the old 10-minute window). Since approval_decide/expire_stale trust the persisted expires_at, a core restart while the copilot request is pending, or a timeout/decide race at the 3-minute boundary, can leave the row actionable for the old window instead of the intended copilot TTL. Please compute the resolved TTL before building the pending row and use the same value for both the DB expiry and the timeout wait.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/approval/gate.rs`:
- Around line 2510-2584: Add an integration regression test alongside the
existing approval-gate tests that scopes APPROVAL_COPILOT_STREAM_CONTEXT while
exercising intercept_audited_inner, then inspect the persisted pending request’s
expires_at to verify the copilot TTL is applied and resolve the request to
complete the flow. Reuse the existing test fixtures, persistence accessors, and
request-resolution helpers rather than testing resolve_park_ttl directly.
- Around line 973-987: Update the approval flow around PendingApproval
construction and Self::resolve_park_ttl so effective_ttl is computed before the
pending row is created, use that clamped duration when setting expires_at, and
reuse the same value for the subsequent timeout instead of calling
self.effective_ttl() earlier.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eadbbdf8-8456-47b4-818e-2b98d24628a1

📥 Commits

Reviewing files that changed from the base of the PR and between 0eeee12 and 48f0c78.

📒 Files selected for processing (3)
  • src/openhuman/approval/gate.rs
  • src/openhuman/approval/mod.rs
  • src/openhuman/flows/ops.rs

Comment thread src/openhuman/approval/gate.rs Outdated
Comment thread src/openhuman/approval/gate.rs
@graycyrus
graycyrus marked this pull request as draft July 22, 2026 09:47
@graycyrus

Copy link
Copy Markdown
Contributor Author

Review feedback addressed in 1f1487ac8 (the fix rides on top of the TTL change). Thanks @coderabbitai — the copilot approval TTL still only affects the flows_build streaming path via the APPROVAL_COPILOT_STREAM_CONTEXT task-local (resolve_park_ttl); main-chat and all other paths keep the 10-min DEFAULT_APPROVAL_TTL. cargo check + fmt clean.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git show --stat --oneline 1f1487ac8
git show --format=fuller --find-renames --find-copies -- \
  1f1487ac8 -- \
  src/openhuman/approval/gate.rs \
  src/openhuman/approval/mod.rs \
  src/openhuman/flows/ops.rs

ast-grep outline src/openhuman/approval/gate.rs --items all --match 'resolve_park_ttl|intercept_audited_inner|APPROVAL_COPILOT_STREAM_CONTEXT'
ast-grep outline src/openhuman/flows/ops.rs --items all --match 'flows_build'

rg -n -C 5 \
  'APPROVAL_COPILOT_STREAM_CONTEXT|resolve_park_ttl|DEFAULT_APPROVAL_TTL|COPILOT_APPROVAL_TTL' \
  src/openhuman/approval/gate.rs src/openhuman/approval/mod.rs src/openhuman/flows/ops.rs

Length of output: 34193


@graycyrus Verified in 1f1487ac8: the clamped TTL is now resolved before persisting expires_at, so the stored expiry and the actual park timeout are consistent. The new regression test covers that copilot-streaming contract, and APPROVAL_COPILOT_STREAM_CONTEXT remains scoped solely to the streaming flows_build branch—other paths retain the 10-minute default.

🐇 ✅

@graycyrus

Copy link
Copy Markdown
Contributor Author

Addressed in 1f1487ac8 (pushed):

  • @coderabbitai / @chatgpt-codex-connector — persist the clamped TTL in expires_at (gate.rs): confirmed valid — expires_at was being built from self.effective_ttl() before the copilot/in-call clamp was resolved, so a copilot-streaming park advertised the old 10-minute expiry in the DB while only actually waiting 180s. Fixed by hoisting Self::resolve_park_ttl(...) up to right after copilot_stream/in_call_ctx are known (both were already available before the expires_at line) and reusing that single resolved value for both the persisted expires_at and the later tokio::time::timeout wait. Removed the now-redundant second resolve_park_ttl call further down.
  • @coderabbitai — add an integration regression test for the scoped path: added copilot_streaming_park_persists_the_clamped_expiry, which builds a gate with the full DEFAULT_APPROVAL_TTL boot TTL (not the 2s test_gate() default, which is already shorter than either clamp and would make the assertion vacuous), scopes APPROVAL_COPILOT_STREAM_CONTEXT alongside APPROVAL_CHAT_CONTEXT + a WebChat origin exactly like flows::ops::flows_build does in production, inspects the persisted expires_at on the pending row to confirm it's clamped to COPILOT_APPROVAL_TTL (not the 10-minute default), then resolves the request and confirms GateOutcome::Allow.

Verified: GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml, GGML_NATIVE=OFF cargo test --lib --manifest-path Cargo.toml approval::gate (48 passed, including the new test), cargo fmt --check on the 3 changed files — all clean. Also re-confirmed the safety property: APPROVAL_COPILOT_STREAM_CONTEXT is scoped at exactly one call site (flows::ops::flows_build's streaming branch), nested inside the existing APPROVAL_CHAT_CONTEXT scope — main-chat and every other approval path are unaffected.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@graycyrus Thanks—both follow-ups are addressed.

Using the single resolved TTL for both expires_at and tokio::time::timeout keeps the persisted approval metadata consistent with runtime behavior. The integration regression test also exercises the intended scoped copilot-streaming path while proving the ordinary default TTL remains unaffected.

🐇 ✅


✏️ Learnings added
Learnt from: graycyrus
URL: https://github.com/tinyhumansai/openhuman/pull/5112

Timestamp: 2026-07-22T10:31:04.413Z
Learning: In `src/openhuman/approval/gate.rs`, any approval TTL selected by `ApprovalGate::resolve_park_ttl` must be used consistently for both the persisted pending request `expires_at` value and the runtime park timeout, so database-visible expiry matches actual gate behavior. `APPROVAL_COPILOT_STREAM_CONTEXT` is intentionally scoped only to the streaming branch of `flows::ops::flows_build`; other approval paths retain `DEFAULT_APPROVAL_TTL`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@graycyrus
graycyrus marked this pull request as ready for review July 22, 2026 10:48
@coderabbitai coderabbitai Bot added the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label Jul 22, 2026

@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: 1f1487ac83

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +4467 to +4469
APPROVAL_CHAT_CONTEXT.scope(
chat_ctx,
APPROVAL_COPILOT_STREAM_CONTEXT.scope((), agent.run_single(&prompt)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Limit the copilot TTL scope to live-run approvals

In a Flow Canvas copilot turn where the builder calls composio_connect, this wraps the entire agent.run_single in APPROVAL_COPILOT_STREAM_CONTEXT, not just run_flow/resume_flow_run. composio_connect is on the workflow_builder belt and manually calls ApprovalGate::intercept_audited_bounded (src/openhuman/composio/tools.rs:923-943); after its 120s bound it deliberately returns while leaving the pending row open so a later Connect-card click can resolve it (src/openhuman/composio/tools.rs:927-964). With this broad scope, that row's expires_at is clamped to 180s, so OAuth/card completion after three minutes is expired/denied instead of remaining actionable for the normal approval TTL. Please scope the copilot task-local only around live-run tool approvals or exempt bounded inline-connect approvals.

Useful? React with 👍 / 👎.

@graycyrus
graycyrus merged commit 58494e1 into tinyhumansai:main Jul 22, 2026
24 of 28 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant