Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a8b23d6
test(openclaw): cover TUI idle timeout regression
chengjiew Jul 6, 2026
9aec0ea
test(openclaw): cover TUI post-idle interactions
chengjiew Jul 6, 2026
22a89ab
test(cli): avoid conditional cache cleanup
chengjiew Jul 6, 2026
dac74e5
merge(main): sync fix branch with main
chengjiew Jul 6, 2026
f1fd44d
test(cli): align oclif runner mock shape
chengjiew Jul 6, 2026
afdd0ae
test(rebuild): accept notice in recovery harness
chengjiew Jul 6, 2026
646f1d0
test(openclaw): cover post-idle network approval
chengjiew Jul 6, 2026
e887c23
test(openclaw): address post-idle review follow-up
chengjiew Jul 6, 2026
c467c94
test(openclaw): tighten network approval markers
chengjiew Jul 6, 2026
933640c
test(openclaw): match issue 6194 session path
chengjiew Jul 6, 2026
ab81f28
test(openclaw): harden issue 6194 follow-up coverage
chengjiew Jul 6, 2026
8c3b773
test(openclaw): avoid appended tui capture
chengjiew Jul 6, 2026
02f22f4
test(openclaw): use sandbox access env for tui expect
chengjiew Jul 6, 2026
8bf393a
test(openclaw): factor issue 6194 expect helper
chengjiew Jul 6, 2026
ed93157
test(openclaw): document issue 6194 repro scope
chengjiew Jul 6, 2026
cbc0dcf
test(openclaw): isolate issue 6194 tui flow
chengjiew Jul 6, 2026
7737d07
Merge remote-tracking branch 'origin/main' into fix/6194_openclaw_tui…
chengjiew Jul 6, 2026
477e9f1
test(openclaw): tighten issue 6194 pr feedback
chengjiew Jul 6, 2026
ba4a799
test(openclaw): cover issue 6194 artifact redaction
chengjiew Jul 6, 2026
b79b7db
merge: sync main into TUI idle coverage
apurvvkumaria Jul 8, 2026
2fa6a09
test(openclaw): keep TUI idle coverage scoped
apurvvkumaria Jul 8, 2026
eceddf6
ci(e2e): install expect for TUI live target
apurvvkumaria Jul 8, 2026
bedfc31
test(openclaw): harden TUI idle live proof
apurvvkumaria Jul 8, 2026
2344f7d
test(e2e): keep workflow boundary linear
apurvvkumaria Jul 8, 2026
456b612
test(openclaw): match rendered slash status
apurvvkumaria Jul 8, 2026
feaf29a
merge(e2e): sync TUI idle coverage with main
apurvvkumaria Jul 9, 2026
b10ba53
merge(e2e): sync latest main
apurvvkumaria Jul 9, 2026
ee349c1
merge(main): sync PR #6296 with latest main
cv Jul 9, 2026
d56841c
test(e2e): reuse shared command result helper
cv Jul 9, 2026
2ca7642
test(openclaw): make network approval proof deterministic
apurvvkumaria Jul 9, 2026
d1f7a7f
test(openclaw): harden TUI exit diagnostics
cv Jul 9, 2026
c0afe57
test(openclaw): avoid capture read race
cv Jul 9, 2026
d5f4002
test(openclaw): avoid contract file race
cv Jul 9, 2026
b692e14
test(e2e): stabilize OpenShell approval PTY
cv Jul 9, 2026
56f9ae3
test(e2e): bind Expect waits to approval PTY
cv Jul 9, 2026
25bd2e6
test(e2e): match stable sandbox detail label
cv Jul 9, 2026
522186c
test(e2e): declare openshell approval boundary
cv Jul 9, 2026
6b0e91a
test(e2e): normalize approval rule output
cv Jul 9, 2026
db9ffca
test(e2e): tolerate styled approval acknowledgement
cv Jul 9, 2026
6073b64
Merge remote-tracking branch 'origin/main' into fix/6194_openclaw_tui…
jyaunches Jul 9, 2026
df8ed1e
test(e2e): lock phase artifact ordering
cv Jul 9, 2026
20e757a
test(e2e): prove approved policy is active
cv Jul 9, 2026
9f658aa
test(e2e): wait for approved policy revision
cv Jul 9, 2026
c82282a
Merge remote-tracking branch 'origin/main' into fix/6194_openclaw_tui…
jyaunches Jul 9, 2026
d1ea65a
test(e2e): allow policy reload deadline
cv Jul 9, 2026
aff5821
Merge remote-tracking branch 'origin/main' into fix/6194_openclaw_tui…
jyaunches Jul 9, 2026
3b0c037
Merge remote-tracking branch 'origin/main' into fix/6194_openclaw_tui…
jyaunches Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4040,6 +4040,33 @@ jobs:

- *dockerhub-auth

# The #6194 terminal regression uses Expect as its PTY driver. Keep this
# privileged host setup inline in the reviewed workflow: the selected
# target ref may consume expect but cannot expand the package allowlist.
# invalidState: hosted-runner Ubuntu mirrors can fail transiently during update.
# sourceBoundary: only this trusted workflow chooses the exact root-installed package.
# whyNotSourceFix: GitHub's Ubuntu image and configured repository move together, so a
# fixed package version would make the target brittle across routine runner refreshes.
# The runner's configured Ubuntu repository is therefore an accepted trust source.
# regressionTest: e2e-host-dependency-workflow-boundary rejects package or order drift.
# removalCondition: remove this step when the hosted image provides Expect itself.
- name: Install OpenClaw TUI host dependencies
shell: bash
run: |
set -euo pipefail
for attempt in 1 2 3; do
if sudo apt-get update; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "::error::apt-get update failed after 3 attempts." >&2
exit 1
fi
echo "::warning::apt-get update attempt ${attempt} failed; retrying." >&2
sleep $((attempt * 5))
done
sudo apt-get install -y --no-install-recommends expect

- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28

Expand Down
343 changes: 343 additions & 0 deletions test/e2e/live/issue-6194-tui-expect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { readFileSync, writeFileSync } from "node:fs";

export const ISSUE6194_TUI_TIMEOUT_SEC = 240;
export const ISSUE6194_TUI_EXIT_TIMEOUT_SEC = 10;
export const ISSUE6194_OPENSHELL_DASHBOARD_TIMEOUT_SEC = 30;
export const ISSUE6194_OPENSHELL_APPROVAL_TIMEOUT_BUFFER_SEC = 120;
export const ISSUE6194_TUI_SESSION_PREFIX = "issue-6194-tui";
export const ISSUE6194_NETWORK_APPROVAL_ENDPOINT =
"https://api.atlassian.com/oauth/token/accessible-resources";
export const ISSUE6194_NETWORK_APPROVAL_HOST = "api.atlassian.com";

export type Issue6194Capture = {
exists: boolean;
contents: string;
};

export function precreateIssue6194Capture(path: string): void {
writeFileSync(path, "", { mode: 0o600 });
}

export function readIssue6194Capture(path: string): Issue6194Capture {
try {
return { exists: true, contents: readFileSync(path, "utf8") };
} catch (error) {
if ((error as { code?: unknown }).code === "ENOENT") {
return { exists: false, contents: "" };
}
throw error;
}
}

export function buildIssue6194TuiExpectScript(): string {
return `set timeout $env(NEMOCLAW_ISSUE_6194_TUI_TIMEOUT)
set sandbox $env(NEMOCLAW_ISSUE_6194_SANDBOX)
set capture $env(NEMOCLAW_ISSUE_6194_CAPTURE)
set session $env(NEMOCLAW_ISSUE_6194_SESSION)
log_file -noappend $capture
proc mark {name} {
puts "ISSUE6194_MARK $name"
send_log "ISSUE6194_MARK $name\\n"
}
proc expect_or_exit {pattern markName timeoutExit eofExit} {
expect {
-nocase -re $pattern { mark $markName }
timeout {
send "\\003"
exit $timeoutExit
}
eof { exit $eofExit }
}
}
spawn openshell sandbox exec --name $sandbox --tty -- sh -lc "export TERM=xterm-256color; cd /sandbox; openclaw tui --session $session"
expect_or_exit {connected[^\\r\\n]*idle} connected_idle_initial 10 11
send -- "Reply with the three fragments joined by underscores: NEMOCLAW6194, CHAT, OK. Put only that joined token on its own line. Do not use tools.\\r"
expect_or_exit {NEMOCLAW6194_CHAT_OK} chat_reply 20 21
expect_or_exit {connected[^\\r\\n]*idle} connected_idle_after_chat 22 23
send -- "/nemoclaw status\\r"
expect_or_exit {NemoClaw Status} slash_status_output 30 31
expect_or_exit {connected[^\\r\\n]*idle} connected_idle_after_status 32 33
# Network-rule approvals belong to the separate OpenShell terminal UI. Keep
# this OpenClaw TUI regression scoped to inputs it can perform directly so a
# tool-less hosted model cannot turn assistant prose into a test oracle.
send "\\003"
set savedTimeout $timeout
set timeout ${ISSUE6194_TUI_EXIT_TIMEOUT_SEC}
expect {
eof {}
-nocase -re {press ctrl\\+c again to exit} {
mark exit_confirmation
send "\\003"
expect {
eof {}
timeout { exit 40 }
}
}
timeout { exit 39 }
}
set timeout $savedTimeout
mark clean_exit
exit 0
`;
}

export function buildIssue6194OpenShellApprovalExpectScript(): string {
return `set timeout $env(NEMOCLAW_ISSUE_6194_TUI_TIMEOUT)
set sandbox $env(NEMOCLAW_ISSUE_6194_SANDBOX)
set capture $env(NEMOCLAW_ISSUE_6194_CAPTURE)
set triggerCapture $env(NEMOCLAW_ISSUE_6194_TRIGGER_CAPTURE)
set ruleCapture $env(NEMOCLAW_ISSUE_6194_RULE_CAPTURE)
set policyCapture $env(NEMOCLAW_ISSUE_6194_POLICY_CAPTURE)
set networkEndpoint $env(NEMOCLAW_ISSUE_6194_NETWORK_ENDPOINT)
set networkHost $env(NEMOCLAW_ISSUE_6194_NETWORK_HOST)
log_file -noappend $capture
proc mark {name} {
puts "ISSUE6194_MARK $name"
send_log "ISSUE6194_MARK $name\\n"
}
proc stop_spawn {target} {
catch {send -i $target "\\003"}
catch {close -i $target}
catch {wait -i $target -nowait}
}
proc write_capture {path value} {
set handle [open $path w]
puts -nonewline $handle $value
close $handle
}
proc expect_or_exit {target pattern markName timeoutExit eofExit} {
expect {
-i $target
-nocase -re $pattern { mark $markName }
timeout {
stop_spawn $target
exit $timeoutExit
}
eof {
catch {wait -i $target}
exit $eofExit
}
}
}
proc expect_exact_or_exit {target value markName timeoutExit eofExit} {
expect {
-i $target
-nocase -ex $value { mark $markName }
timeout {
stop_spawn $target
exit $timeoutExit
}
eof {
catch {wait -i $target}
exit $eofExit
}
}
}
# The OpenShell TUI starts on Gateways. Refuse to navigate by position unless
# this ephemeral target owns exactly one sandbox.
if {[catch {exec openshell sandbox list --names} sandboxNames]} {
puts "ISSUE6194_DIAGNOSTIC sandbox listing failed: $sandboxNames"
exit 60
}
if {[string trim $sandboxNames] ne $sandbox} {
puts "ISSUE6194_DIAGNOSTIC expected sole sandbox '$sandbox', got: $sandboxNames"
exit 61
}
mark sole_sandbox_verified
# Do not clear unexpected rules: a pre-existing or concurrent pending request
# must fail this target instead of being hidden or accidentally approved.
if {[catch {exec openshell rule get $sandbox --status pending} pendingBefore]} {
puts "ISSUE6194_DIAGNOSTIC pending-rule preflight failed: $pendingBefore"
exit 62
}
set expectedEmpty "No network rules for sandbox '$sandbox'"
if {[string trim $pendingBefore] ne $expectedEmpty} {
puts "ISSUE6194_DIAGNOSTIC pending-rule queue was not empty: $pendingBefore"
exit 63
}
mark pending_queue_empty
# ShellProbe has no controlling TTY. Pin the terminal type and Expect-owned
# PTY geometry before OpenShell performs its first full-screen render.
set env(TERM) xterm-256color
spawn openshell term
set termSpawn $spawn_id
set termPty $spawn_out(slave,name)
stty rows 40 columns 120 < $termPty
set dashboardTimeout $timeout
set timeout ${ISSUE6194_OPENSHELL_DASHBOARD_TIMEOUT_SEC}
expect_exact_or_exit $termSpawn {Sandboxes} openshell_dashboard 64 65
set timeout $dashboardTimeout
# Gateways -> Providers -> Sandboxes.
send -i $termSpawn -- "\\t"
after 200
send -i $termSpawn -- "\\t"
after 200
expect_exact_or_exit $termSpawn $sandbox openshell_sandbox_listed 66 67
send -i $termSpawn -- "\\r"
expect_exact_or_exit $termSpawn {Name:} openshell_sandbox_detail 68 69
expect_exact_or_exit $termSpawn $sandbox openshell_sandbox_detail_name 70 71
# OpenShell documents 'r' as the Network Rules focus key in sandbox detail.
send -i $termSpawn -- "r"
expect_exact_or_exit $termSpawn {Network Rules} network_rules_focused 72 73
# The request uses argv boundaries and hard time limits. Its output belongs to
# a separate spawn, so it cannot satisfy any openshell term UI assertion.
spawn -noecho openshell sandbox exec --name $sandbox --no-tty --timeout 40 -- /usr/bin/curl -sS --connect-timeout 5 --max-time 30 -o /dev/null $networkEndpoint
set curlSpawn $spawn_id
mark network_request_triggered
set savedTimeout $timeout
set timeout 45
expect {
-i $curlSpawn
eof { set triggerOutput $expect_out(buffer) }
timeout {
stop_spawn $curlSpawn
stop_spawn $termSpawn
exit 74
}
}
set timeout $savedTimeout
catch {wait -i $curlSpawn} curlWait
write_capture $triggerCapture $triggerOutput
mark network_request_completed
# Poll the supported rule CLI until it proves there is exactly one pending
# chunk and that it belongs to this exact curl/endpoint pair.
set pendingOutput ""
set pendingReady 0
for {set attempt 0} {$attempt < 20} {incr attempt} {
set pendingGetFailed [catch {exec openshell rule get $sandbox --status pending} candidate]
# OpenShell may color labels even when output is captured. Strip SGR before
# parsing and publishing the rule evidence so label/value matches remain
# deterministic across runner terminals and OpenShell render modes.
regsub -all {\\x1b\\[[0-9;]*m} $candidate "" pendingOutput
if {!$pendingGetFailed} {
set chunkCount [regexp -all -line {^[[:space:]]*Chunk:} $pendingOutput]
set oneChunk [regexp -nocase {Network Rules:[^\\r\\n]*1 chunk} $pendingOutput]
set pendingStatus [regexp -nocase {Status:[[:space:]]*pending} $pendingOutput]
set curlBinary [regexp {Binary:[[:space:]]*/usr/bin/curl} $pendingOutput]
set expectedEndpoint [expr {[string first $networkHost $pendingOutput] >= 0}]
if {$chunkCount == 1 && $oneChunk && $pendingStatus && $curlBinary && $expectedEndpoint} {
set pendingReady 1
break
}
}
after 500
}
write_capture $ruleCapture $pendingOutput
if {!$pendingReady} {
puts "ISSUE6194_DIAGNOSTIC expected one curl pending rule, got: $pendingOutput"
stop_spawn $termSpawn
exit 75
}
mark network_rule_singleton
# Only the OpenShell term spawn can satisfy these patterns. The trigger output
# and assistant transcript are isolated from this buffer.
expect_exact_or_exit $termSpawn $networkHost network_rule_endpoint 76 77
send -i $termSpawn -- "\\r"
expect_or_exit $termSpawn {Status:[^\\r\\n]*pending} network_rule_detail 78 79
expect_or_exit $termSpawn {Binary:[^\\r\\n]*/usr/bin/curl} network_rule_detail_binary 80 81
expect_exact_or_exit $termSpawn $networkHost network_rule_detail_endpoint 82 83
expect_or_exit $termSpawn {\\[a\\][^\\r\\n]*Approve} network_rule_approve_action 84 85
send -i $termSpawn -- "a"
expect {
-i $termSpawn
-nocase -re {Approved[^\\r\\n]*'[^']+'[^\\r\\n]*policy v([0-9]+)} {
set approvedPolicyVersion $expect_out(1,string)
mark network_approval_processed
}
timeout {
stop_spawn $termSpawn
exit 86
}
eof {
catch {wait -i $termSpawn}
exit 87
}
}
# The approval RPC assigns a policy revision before the sandbox loads it.
# Poll that exact revision through the read-only policy API until both its
# status and the active version prove convergence. Preserve every bounded
# attempt so timeout and failed-revision diagnostics remain reviewable.
set policyStatusOutput "ISSUE6194_APPROVED_POLICY_VERSION=$approvedPolicyVersion\\n"
set policyLoaded 0
set policyTerminalStatus timeout
set policyLoadDeadline [expr {[clock milliseconds] + 60000}]
set attempt 0
while {[clock milliseconds] < $policyLoadDeadline} {
incr attempt
set policyGetFailed [catch {exec timeout 2 openshell policy get $sandbox --rev $approvedPolicyVersion --output json} candidate]
append policyStatusOutput "ISSUE6194_POLICY_STATUS_ATTEMPT=$attempt\\n$candidate\\n"
set versionPattern [format {"version"[[:space:]]*:[[:space:]]*%s([[:space:]]|,)} $approvedPolicyVersion]
set activePattern [format {"active_version"[[:space:]]*:[[:space:]]*%s([[:space:]]|,)} $approvedPolicyVersion]
set versionMatches [regexp $versionPattern $candidate]
set statusLoaded [regexp {"status"[[:space:]]*:[[:space:]]*"loaded"} $candidate]
set activeMatches [regexp $activePattern $candidate]
if {!$policyGetFailed && $versionMatches && $statusLoaded && $activeMatches} {
append policyStatusOutput "ISSUE6194_ACTIVE_POLICY_VERSION=$approvedPolicyVersion\\n"
append policyStatusOutput "ISSUE6194_POLICY_STATUS=loaded\\n"
set policyLoaded 1
break
}
if {!$policyGetFailed && [regexp {"status"[[:space:]]*:[[:space:]]*"(failed|superseded)"} $candidate _ terminalStatus]} {
set policyTerminalStatus $terminalStatus
break
}
after 1000
}
if {!$policyLoaded} {
append policyStatusOutput "ISSUE6194_POLICY_STATUS=$policyTerminalStatus\\n"
write_capture $policyCapture $policyStatusOutput
puts "ISSUE6194_DIAGNOSTIC approved policy revision did not become active: $policyStatusOutput"
stop_spawn $termSpawn
exit 90
}
mark network_policy_loaded
# Retry the exact documented Atlassian probe once the acknowledged policy
# revision is active. An unauthenticated 401 is the expected success signal.
spawn -noecho openshell sandbox exec --name $sandbox --no-tty --timeout 20 -- /usr/bin/curl -sS --connect-timeout 5 --max-time 10 -o /dev/null -w {ISSUE6194_POLICY_HTTP_STATUS=%{http_code}\\n} $networkEndpoint
set policySpawn $spawn_id
set policyOutput ""
set savedTimeout $timeout
set timeout 25
expect {
-i $policySpawn
eof { set policyOutput $expect_out(buffer) }
timeout {
write_capture $policyCapture $policyStatusOutput
stop_spawn $policySpawn
stop_spawn $termSpawn
exit 88
}
}
set timeout $savedTimeout
catch {wait -i $policySpawn} policyWait
write_capture $policyCapture "$policyStatusOutput$policyOutput"
if {![regexp {ISSUE6194_POLICY_HTTP_STATUS=401(\\r?\\n|$)} $policyOutput]} {
puts "ISSUE6194_DIAGNOSTIC approved policy did not permit the exact endpoint: $policyOutput"
stop_spawn $termSpawn
exit 91
}
mark network_policy_updated
send -i $termSpawn -- "q"
expect {
-i $termSpawn
eof {}
timeout {
send -i $termSpawn "\\003"
expect {
-i $termSpawn
eof {}
timeout {
stop_spawn $termSpawn
exit 89
}
}
}
}
catch {wait -i $termSpawn} termWait
mark openshell_clean_exit
exit 0
`;
}
Loading