-
Notifications
You must be signed in to change notification settings - Fork 2
fix(cli): derive repo log levels #407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@gh-symphony/cli": patch | ||
| --- | ||
|
|
||
| Fix `gh-symphony repo logs --level` to derive levels from structured event types, include turn failures in error results, validate unsupported level values, and report empty filtered results clearly for issue #386. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,8 @@ import { | |
| } from "../project-selection.js"; | ||
|
|
||
| type LoggedEvent = Record<string, unknown> & { at?: string }; | ||
| type LogLevel = "error" | "warn" | "info"; | ||
| const LOG_LEVELS: readonly LogLevel[] = ["error", "warn", "info"]; | ||
|
|
||
| function parseLogsArgs(args: string[]): { | ||
| follow: boolean; | ||
|
|
@@ -56,6 +58,16 @@ const handler = async ( | |
| options: GlobalOptions | ||
| ): Promise<void> => { | ||
| const parsed = parseLogsArgs(args); | ||
| const level = parseLogLevel(parsed.level); | ||
| if (parsed.level && !level) { | ||
| process.stderr.write( | ||
| `Unknown --level "${parsed.level}". Valid values: ${LOG_LEVELS.join( | ||
| ", " | ||
| )}.\n` | ||
| ); | ||
| process.exitCode = 1; | ||
| return; | ||
| } | ||
| const runtimeRoot = resolve(options.configDir); | ||
|
|
||
| // If --run is specified, read that run's events | ||
|
|
@@ -78,14 +90,19 @@ const handler = async ( | |
| try { | ||
| const content = await readFile(eventsPath, "utf8"); | ||
| const lines = content.trim().split("\n").filter(Boolean); | ||
| let matchedEvents = 0; | ||
| for (const line of lines) { | ||
|
Comment on lines
90
to
94
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in |
||
| const event = JSON.parse(line) as LoggedEvent; | ||
| if (parsed.projectId && getProjectId(event) !== parsed.projectId) | ||
| continue; | ||
| if (parsed.level && getLevel(event) !== parsed.level) continue; | ||
| if (level && getLevel(event) !== level) continue; | ||
| if (parsed.issue && getIssueIdentifier(event) !== parsed.issue) | ||
| continue; | ||
| process.stdout.write(formatEvent(event) + "\n"); | ||
| matchedEvents += 1; | ||
| } | ||
| if (level && matchedEvents === 0) { | ||
| process.stderr.write(noMatchedEventsMessage(level)); | ||
| } | ||
| } catch { | ||
| process.stderr.write(`No events found for run: ${parsed.run}\n`); | ||
|
|
@@ -147,6 +164,7 @@ const handler = async ( | |
| ? [join(runtimeRoot, "projects", parsed.projectId, "runs")] | ||
| : await listProjectRunRoots(runtimeRoot); | ||
| let foundRuns = false; | ||
| let matchedEvents = 0; | ||
| const events: LoggedEvent[] = []; | ||
|
Comment on lines
166
to
168
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in |
||
|
|
||
| try { | ||
|
|
@@ -165,10 +183,11 @@ const handler = async ( | |
| const event = JSON.parse(line) as LoggedEvent; | ||
| if (parsed.projectId && getProjectId(event) !== parsed.projectId) | ||
| continue; | ||
| if (parsed.level && getLevel(event) !== parsed.level) continue; | ||
| if (level && getLevel(event) !== level) continue; | ||
| if (parsed.issue && getIssueIdentifier(event) !== parsed.issue) | ||
| continue; | ||
| events.push(event); | ||
| matchedEvents += 1; | ||
| } | ||
| } catch { | ||
| // Skip runs without events | ||
|
|
@@ -183,6 +202,10 @@ const handler = async ( | |
| process.stderr.write("No runs found. Start the orchestrator first.\n"); | ||
| return; | ||
| } | ||
| if (level && matchedEvents === 0) { | ||
| process.stderr.write(noMatchedEventsMessage(level)); | ||
| return; | ||
| } | ||
|
|
||
| events.sort((left, right) => | ||
| String(left.at ?? "").localeCompare(String(right.at ?? "")) | ||
|
|
@@ -208,8 +231,32 @@ function getProjectId(event: LoggedEvent): string | undefined { | |
| return typeof event.projectId === "string" ? event.projectId : undefined; | ||
| } | ||
|
|
||
| function getLevel(event: LoggedEvent): string | undefined { | ||
| return typeof event.level === "string" ? event.level : undefined; | ||
| function getLevel(event: LoggedEvent): LogLevel { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit (non-blocking): Generated by Claude Code
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accepted and resolved in |
||
| switch (event.event) { | ||
| case "run-failed": | ||
| case "turn_failed": | ||
| case "worker-error": | ||
| case "hook-failed": | ||
| return "error"; | ||
|
Comment on lines
+236
to
+240
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a worker turn fails, the orchestrator writes a structured run event with Useful? React with 👍 / 👎.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in |
||
| case "run-suppressed": | ||
| case "run-retried": | ||
| return "warn"; | ||
| default: | ||
| return "info"; | ||
| } | ||
| } | ||
|
Comment on lines
+234
to
247
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in |
||
|
|
||
| function parseLogLevel(level: string | undefined): LogLevel | undefined { | ||
| if (!level) { | ||
| return undefined; | ||
| } | ||
| return LOG_LEVELS.includes(level as LogLevel) | ||
| ? (level as LogLevel) | ||
| : undefined; | ||
| } | ||
|
|
||
| function noMatchedEventsMessage(level: LogLevel): string { | ||
| return `No events matched the provided filters (--level ${level}).\n`; | ||
| } | ||
|
|
||
| function getIssueIdentifier(event: LoggedEvent): string { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in
aaa1855: added a regression test for--run <id> --level errorwhere the run has events but none match the derived level.