mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-14 09:46:06 +00:00
fix(ci): retain Telegram candidate diagnostics (#105472)
* fix(ci): retain Telegram candidate diagnostics * fix(ci): stream redacted Telegram diagnostics
This commit is contained in:
committed by
GitHub
parent
761839343a
commit
eaedcdcd4e
154
.github/workflows/openclaw-release-telegram-qa.yml
vendored
154
.github/workflows/openclaw-release-telegram-qa.yml
vendored
@@ -2164,6 +2164,159 @@ jobs:
|
||||
echo "Isolated Telegram SUT UID still owns processes after terminal cleanup." >&2
|
||||
exit 1
|
||||
|
||||
- name: Capture isolated Telegram runtime diagnostics
|
||||
id: capture_diagnostics
|
||||
if: always() && steps.terminate_sut.outputs.quiescent == 'true' && steps.run_lane.outputs.output_dir != ''
|
||||
env:
|
||||
OUTPUT_DIR: ${{ steps.run_lane.outputs.output_dir }}
|
||||
RUNTIME_ROOT: ${{ steps.create_sut.outputs.runtime_root }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
[[ "$RUNTIME_ROOT" == "/var/lib/openclaw-telegram-sut-runtime-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" ]]
|
||||
diagnostics_dir="${OUTPUT_DIR}/trusted-runtime-diagnostics"
|
||||
install -d -m 0700 "$diagnostics_dir"
|
||||
trusted_temp_root="$(mktemp -d "${RUNNER_TEMP}/openclaw-telegram-diagnostics.XXXXXX")"
|
||||
trap 'rm -rf -- "$trusted_temp_root"' EXIT
|
||||
redactor_script="${trusted_temp_root}/redact-gateway-tail.mts"
|
||||
cat >"$redactor_script" <<'NODE'
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const [outputPath] = process.argv.slice(2);
|
||||
const redactorPath = path.resolve(
|
||||
"extensions/qa-lab/src/gateway-log-redaction.ts",
|
||||
);
|
||||
const { redactQaGatewayDebugText } = await import(pathToFileURL(redactorPath).href);
|
||||
const limitBytes = 131_072;
|
||||
const maxInputRecordBytes = 1_048_576;
|
||||
const oversizedMarker = "[truncated oversized gateway log record] ";
|
||||
const omittedMarker = "[omitted oversized gateway log record]\n";
|
||||
const retained = [];
|
||||
let retainedBytes = 0;
|
||||
|
||||
function boundRedactedLine(line) {
|
||||
const redactedLine = `${redactQaGatewayDebugText(line)}\n`;
|
||||
const lineBuffer = Buffer.from(redactedLine);
|
||||
if (lineBuffer.length <= limitBytes) {
|
||||
return redactedLine;
|
||||
}
|
||||
const markerBytes = Buffer.byteLength(oversizedMarker);
|
||||
const suffixBuffer = lineBuffer.subarray(
|
||||
lineBuffer.length - (limitBytes - markerBytes),
|
||||
);
|
||||
let suffix = suffixBuffer.toString("utf8");
|
||||
while (Buffer.byteLength(`${oversizedMarker}${suffix}`) > limitBytes) {
|
||||
suffix = suffix.slice(1);
|
||||
}
|
||||
return `${oversizedMarker}${suffix}`;
|
||||
}
|
||||
|
||||
function retainLine(boundedLine) {
|
||||
const lineBytes = Buffer.byteLength(boundedLine);
|
||||
while (retained.length > 0 && retainedBytes + lineBytes > limitBytes) {
|
||||
const removed = retained.shift();
|
||||
retainedBytes -= Buffer.byteLength(removed);
|
||||
}
|
||||
retained.push(boundedLine);
|
||||
retainedBytes += lineBytes;
|
||||
}
|
||||
|
||||
let recordParts = [];
|
||||
let recordBytes = 0;
|
||||
let recordOmitted = false;
|
||||
function appendRecordPart(part) {
|
||||
if (recordOmitted) {
|
||||
return;
|
||||
}
|
||||
if (recordBytes + part.length > maxInputRecordBytes) {
|
||||
recordParts = [];
|
||||
recordBytes = 0;
|
||||
recordOmitted = true;
|
||||
return;
|
||||
}
|
||||
recordParts.push(Buffer.from(part));
|
||||
recordBytes += part.length;
|
||||
}
|
||||
function finishRecord() {
|
||||
if (recordOmitted) {
|
||||
retainLine(omittedMarker);
|
||||
} else {
|
||||
const line = Buffer.concat(recordParts, recordBytes).toString("utf8").replace(/\r$/u, "");
|
||||
retainLine(boundRedactedLine(line));
|
||||
}
|
||||
recordParts = [];
|
||||
recordBytes = 0;
|
||||
recordOmitted = false;
|
||||
}
|
||||
|
||||
for await (const value of process.stdin) {
|
||||
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
||||
let offset = 0;
|
||||
while (offset < chunk.length) {
|
||||
const newline = chunk.indexOf(0x0a, offset);
|
||||
const end = newline === -1 ? chunk.length : newline;
|
||||
appendRecordPart(chunk.subarray(offset, end));
|
||||
if (newline === -1) {
|
||||
break;
|
||||
}
|
||||
finishRecord();
|
||||
offset = newline + 1;
|
||||
}
|
||||
}
|
||||
if (recordOmitted || recordBytes > 0) {
|
||||
finishRecord();
|
||||
}
|
||||
fs.writeFileSync(outputPath, retained.join(""), { encoding: "utf8", mode: 0o600 });
|
||||
NODE
|
||||
chmod 0600 "$redactor_script"
|
||||
|
||||
mapfile -t gateway_logs < <(
|
||||
sudo find "$RUNTIME_ROOT/tmp" -xdev -type f -name 'openclaw-*.log' -print | sort
|
||||
)
|
||||
((${#gateway_logs[@]} > 0 && ${#gateway_logs[@]} <= 8))
|
||||
for index in "${!gateway_logs[@]}"; do
|
||||
log_path="$(sudo realpath -e "${gateway_logs[$index]}")"
|
||||
case "$log_path" in
|
||||
"$RUNTIME_ROOT"/tmp/*) ;;
|
||||
*) echo "Telegram gateway log escaped the isolated runtime root." >&2; exit 1 ;;
|
||||
esac
|
||||
[[ "$(sudo stat -c '%F' "$log_path")" == "regular file" ]]
|
||||
output_path="${diagnostics_dir}/gateway-internal-tail-$((index + 1)).log"
|
||||
sudo cat "$log_path" | node --import tsx "$redactor_script" "$output_path"
|
||||
done
|
||||
|
||||
mapfile -t gateway_configs < <(
|
||||
sudo find "$RUNTIME_ROOT/tmp" -xdev -type f -name openclaw.json -print | sort
|
||||
)
|
||||
((${#gateway_configs[@]} > 0 && ${#gateway_configs[@]} <= 8))
|
||||
for index in "${!gateway_configs[@]}"; do
|
||||
config_path="$(sudo realpath -e "${gateway_configs[$index]}")"
|
||||
case "$config_path" in
|
||||
"$RUNTIME_ROOT"/tmp/*) ;;
|
||||
*) echo "Telegram gateway config escaped the isolated runtime root." >&2; exit 1 ;;
|
||||
esac
|
||||
proof_path="${diagnostics_dir}/gateway-model-config-$((index + 1)).json"
|
||||
sudo jq -e '
|
||||
{
|
||||
agentDefaultModel: .agents.defaults.model,
|
||||
agentModelRefs: ((.agents.defaults.models // {}) | keys),
|
||||
providers: (
|
||||
(.models.providers // {}) |
|
||||
to_entries |
|
||||
map({
|
||||
id: .key,
|
||||
api: .value.api,
|
||||
modelIds: [.value.models[]?.id]
|
||||
})
|
||||
)
|
||||
}
|
||||
' "$config_path" >"$proof_path"
|
||||
chmod 0600 "$proof_path"
|
||||
done
|
||||
|
||||
- name: Finalize trusted Telegram process-boundary evidence
|
||||
id: finalize_boundary
|
||||
if: always() && steps.terminate_sut.outputs.quiescent == 'true' && steps.run_lane.outputs.output_dir != ''
|
||||
@@ -2360,6 +2513,7 @@ jobs:
|
||||
${{ steps.validate_credentials.outcome }}
|
||||
${{ steps.run_lane.outcome }}
|
||||
${{ steps.terminate_sut.outcome }}
|
||||
${{ steps.capture_diagnostics.outcome }}
|
||||
${{ steps.finalize_boundary.outcome }}
|
||||
${{ steps.validate_evidence.outcome }}
|
||||
${{ steps.remove_sut.outcome }}
|
||||
|
||||
@@ -557,6 +557,44 @@ describe("release Telegram QA workflow", () => {
|
||||
expect(finalizeStep?.env?.RUN_LANE_OUTCOME).toBe("${{ steps.run_lane.outcome }}");
|
||||
expect(finalizeStep?.run).toContain('--arg runLaneOutcome "$RUN_LANE_OUTCOME"');
|
||||
expect(finalizeStep?.run).toContain('if $runLaneOutcome == "success" then 2 else 1 end');
|
||||
|
||||
const captureStep = job?.steps?.find(
|
||||
(step) => step.name === "Capture isolated Telegram runtime diagnostics",
|
||||
);
|
||||
expect(captureStep?.if).toContain("steps.terminate_sut.outputs.quiescent == 'true'");
|
||||
expect(captureStep?.env?.OUTPUT_DIR).toBe("${{ steps.run_lane.outputs.output_dir }}");
|
||||
expect(captureStep?.env?.RUNTIME_ROOT).toBe("${{ steps.create_sut.outputs.runtime_root }}");
|
||||
expect(captureStep?.run).toContain("-name 'openclaw-*.log'");
|
||||
expect(captureStep?.run).toContain(
|
||||
'trusted_temp_root="$(mktemp -d "${RUNNER_TEMP}/openclaw-telegram-diagnostics.XXXXXX")"',
|
||||
);
|
||||
expect(captureStep?.run).not.toContain(".raw");
|
||||
expect(captureStep?.run).toContain(
|
||||
'sudo cat "$log_path" | node --import tsx "$redactor_script" "$output_path"',
|
||||
);
|
||||
expect(captureStep?.run).toContain("const redactedLine =");
|
||||
expect(captureStep?.run).toContain("const limitBytes = 131_072");
|
||||
expect(captureStep?.run).toContain("const maxInputRecordBytes = 1_048_576");
|
||||
expect(captureStep?.run).toContain("[truncated oversized gateway log record]");
|
||||
expect(captureStep?.run).toContain("[omitted oversized gateway log record]");
|
||||
expect(captureStep?.run).toContain("chunk.indexOf(0x0a, offset)");
|
||||
expect(captureStep?.run).not.toContain("readline.createInterface");
|
||||
expect(captureStep?.run).toContain("while (retained.length > 0");
|
||||
expect(captureStep?.run).toContain("redactQaGatewayDebugText");
|
||||
expect(captureStep?.run).toContain("agentDefaultModel: .agents.defaults.model");
|
||||
expect(captureStep?.run).toContain("modelIds: [.value.models[]?.id]");
|
||||
expect(
|
||||
job?.steps?.findIndex(
|
||||
(step) => step.name === "Capture isolated Telegram runtime diagnostics",
|
||||
),
|
||||
).toBeLessThan(
|
||||
job?.steps?.findIndex(
|
||||
(step) => step.name === "Finalize trusted Telegram process-boundary evidence",
|
||||
) ?? -1,
|
||||
);
|
||||
|
||||
const recordStep = job?.steps?.find((step) => step.name === "Record Telegram execution status");
|
||||
expect(recordStep?.env?.OUTCOMES).toContain("${{ steps.capture_diagnostics.outcome }}");
|
||||
});
|
||||
|
||||
it("serializes stderr behind the workflow-command pause", () => {
|
||||
|
||||
Reference in New Issue
Block a user