polish report drafting guidance

This commit is contained in:
Tak Hoffman
2026-03-21 12:39:23 -05:00
parent a18f9bc29c
commit 5d8750cdab
3 changed files with 59 additions and 6 deletions

View File

@@ -46,6 +46,7 @@ Use this skill only for `openclaw/openclaw`.
- Show the full sanitized draft before asking to submit.
- Ask permission in plain English before adding `--submit`.
- Only after approval, use `--submit` for public bug or feature issues.
- If submission succeeds, include the created GitHub issue URL in the final reply to the user.
## Do Not
@@ -99,7 +100,8 @@ Use this skill only for `openclaw/openclaw`.
10. After showing the draft, ask in plain English: `If this draft looks right, I can submit it to GitHub now.`
11. Do not mention CLI flags like `--submit` in the user-facing approval question.
12. Only if the user clearly approves, rerun or continue with `--submit` for public bug or feature issues.
13. For security, keep the report private and route the user to `security@openclaw.ai`.
13. If the issue is created successfully, include the created GitHub URL in the final reply.
14. For security, keep the report private and route the user to `security@openclaw.ai`.
## Common Commands
@@ -131,6 +133,8 @@ Use `--additional-information` for details that do not fit neatly into `--repro`
Prefer deriving these from the conversation when they are already clear instead of asking the user to restate them.
When passing multiline text into the CLI as a single quoted argument, encode line breaks as literal `\n` so `openclaw report` can render them back as real line breaks in the final issue body.
## Private Security Reports
If the request is a security issue:
@@ -179,7 +183,7 @@ I need a few bug-report details: what steps reproduce it, what you expected, wha
If those fields are not already clear from the diagnosis session, ask for them. Otherwise infer them and run:
```bash
openclaw report bug --summary "Gateway times out behind mitmproxy" --repro "..." --expected "..." --actual "..." --impact "..." --probe gateway
openclaw report bug --summary "Gateway times out behind mitmproxy" --repro "1. Start gateway\n2. Send request\n3. Observe timeout" --expected "..." --actual "..." --impact "..." --probe gateway
```
Then show the draft itself, not internal report metadata. Only add `--submit` after explicit approval.
@@ -190,6 +194,8 @@ After showing the draft, ask:
If this draft looks right, I can submit it to GitHub now.
```
If submission succeeds, include the created issue URL in the final reply.
### Example: regression bug with loose extra context
User says: `This worked last week, but after updating it started timing out.`
@@ -206,7 +212,7 @@ I need the repro steps, expected behavior, actual behavior, and impact. I can al
If the missing facts are not already clear from the conversation or diagnosis session, ask for them. Otherwise infer them and run:
```bash
openclaw report bug --summary "Regression after update" --repro "..." --expected "..." --actual "..." --impact "..." --previous-version "2026.3.14" --additional-information "Worked last week; now every call times out behind the same proxy setup." --probe model
openclaw report bug --summary "Regression after update" --repro "1. Start gateway\n2. Send request\n3. Observe failure" --expected "..." --actual "..." --impact "..." --previous-version "2026.3.14" --additional-information "Worked last week; now every call times out behind the same proxy setup." --probe model
```
Then show the draft and ask:
@@ -215,6 +221,8 @@ Then show the draft and ask:
If this draft looks right, I can submit it to GitHub now.
```
If submission succeeds, include the created issue URL in the final reply.
### Example: feature request
User says: `Please add a way to export a report draft without submitting it.`
@@ -231,6 +239,8 @@ Then show the draft and ask:
If this draft looks right, I can submit it to GitHub now.
```
If submission succeeds, include the created issue URL in the final reply.
### Example: private security report
User says: `I found a token leak in logs.`

View File

@@ -202,6 +202,22 @@ describe("reportCommand", () => {
expect(payload.submissionEligible).toBe(false);
});
it("renders literal escaped newline sequences as real line breaks in report sections", async () => {
const payload = await buildReportPayload({
kind: "bug",
options: {
summary: "Gateway timeout",
repro: "1. Start gateway\\n2. Send request\\n3. Observe failure",
expected: "Model responds",
actual: "Timeout",
impact: "Blocks requests",
},
});
expect(payload.body).toContain("1. Start gateway\n2. Send request\n3. Observe failure");
expect(payload.body).not.toContain("\\n2. Send request");
});
it("writes markdown output that matches the generated body", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-report-test-"));
const outputPath = path.join(tmpDir, "bug.md");
@@ -446,6 +462,25 @@ describe("reportCommand", () => {
expect(payload.submission.url).toBe("https://github.com/openclaw/openclaw/issues/999");
});
it("prints the created issue url in human output after successful submission", async () => {
await reportCommand({
kind: "bug",
options: {
summary: "Gateway timeout",
repro: "1. Start gateway",
expected: "Model responds",
actual: "Timeout",
impact: "Blocks requests",
submit: true,
yes: true,
nonInteractive: true,
},
runtime,
});
expect(runtime.log).toHaveBeenCalledWith("Created: https://github.com/openclaw/openclaw/issues/999");
});
it("keeps draft generation working when config and secret resolution degrade", async () => {
readBestEffortConfig.mockRejectedValueOnce(new Error("config missing"));
resolveCommandSecretRefsViaGateway.mockRejectedValueOnce(new Error("secret refs unavailable"));

View File

@@ -137,8 +137,15 @@ export type ReportPayload = {
const PUBLIC_REPO = "openclaw/openclaw";
const REPORT_GENERATED_NOTE = "_Generated via `openclaw report`._";
function normalizeEscapedMultilineText(value: string | undefined): string | undefined {
if (value === undefined) {
return undefined;
}
return value.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\r\n/g, "\n");
}
function sanitizeWhitespace(value: string | undefined): string | undefined {
const trimmed = value?.trim();
const trimmed = normalizeEscapedMultilineText(value)?.trim();
return trimmed ? trimmed : undefined;
}
@@ -165,8 +172,9 @@ export function sanitizeReportText(value: string | undefined): {
if (!value) {
return { text: "", redactionsApplied: [] };
}
const redactions = detectRedactions(value);
let text = redactSecrets(value);
const normalized = normalizeEscapedMultilineText(value) ?? "";
const redactions = detectRedactions(normalized);
let text = redactSecrets(normalized);
text = text.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "[email-redacted]");
text = text.replace(/\b(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?){2}\d{4}\b/g, "[phone-redacted]");
text = text.replace(/\/Users\/[^/\s]+/g, "/Users/user");