Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 18 additions & 13 deletions .github/extensions/agentic-workflows-dashboard/extension.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@ let workspacePath = process.cwd();

function execp(bin, args, cwd) {
return new Promise((resolve, reject) => {
execFile(bin, args, {
cwd,
env: { ...process.env, NO_COLOR: "1", GH_NO_UPDATE_NOTIFIER: "1" },
maxBuffer: 10 * 1024 * 1024,
}, (err, stdout, stderr) => {
if (err) reject(Object.assign(err, { stderr: stderr ?? "" }));
else resolve(stdout);
});
execFile(
bin,
args,
{
cwd,
env: { ...process.env, NO_COLOR: "1", GH_NO_UPDATE_NOTIFIER: "1" },
maxBuffer: 10 * 1024 * 1024,
},
(err, stdout, stderr) => {
if (err) reject(Object.assign(err, { stderr: stderr ?? "" }));
else resolve(stdout);
}
);
});
}

Expand Down Expand Up @@ -136,10 +141,7 @@ async function startServer() {

try {
if (pathname === "/" || pathname === "/index.html") {
const [html, css] = await Promise.all([
readFile(join(__dirname, "web", "index.html"), "utf8"),
readFile(join(__dirname, "web", "styles.css"), "utf8"),
]);
const [html, css] = await Promise.all([readFile(join(__dirname, "web", "index.html"), "utf8"), readFile(join(__dirname, "web", "styles.css"), "utf8")]);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(html.replace("/*__APP_CSS__*/", css));
} else if (pathname === "/app.js") {
Expand Down Expand Up @@ -268,7 +270,10 @@ It never calls Go code directly — all data is fetched by running CLI subcomman
name: "refresh",
description: "Clear the data cache so the next listDefinitions/listRuns fetches fresh data from the CLI.",
inputSchema: { type: "object", additionalProperties: false },
handler: () => { cache.clear(); return { ok: true }; },
handler: () => {
cache.clear();
return { ok: true };
},
},
],
open: async ctx => {
Expand Down
16 changes: 4 additions & 12 deletions .github/extensions/agentic-workflows-dashboard/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,14 @@ <h2 class="Box-title">Workflow definitions <span class="Counter" x-text="definit
<div>
<div class="text-bold" x-text="definition.workflow"></div>
<div class="color-fg-muted text-small mt-1">Engine: <span x-text="definition.engine_id ?? '—'"></span></div>
<div class="color-fg-muted text-small" x-show="definition.labels && definition.labels.length">
Labels: <span x-text="(definition.labels ?? []).join(', ')"></span>
</div>
<div class="color-fg-muted text-small" x-show="definition.time_remaining">
Time remaining: <span x-text="definition.time_remaining"></span>
</div>
<div class="color-fg-muted text-small" x-show="definition.labels && definition.labels.length">Labels: <span x-text="(definition.labels ?? []).join(', ')"></span></div>
<div class="color-fg-muted text-small" x-show="definition.time_remaining">Time remaining: <span x-text="definition.time_remaining"></span></div>
</div>
<span :class="definitionStatusClass(definition)" x-text="definitionStatusLabel(definition)"></span>
</div>
</div>
</template>
<div class="Box-row color-fg-muted text-center py-3" x-show="!loadingDefinitions && definitionsPaged.totalItems === 0">
No workflow definitions found. Run <code>make build</code> then open this canvas again.
</div>
<div class="Box-row color-fg-muted text-center py-3" x-show="!loadingDefinitions && definitionsPaged.totalItems === 0">No workflow definitions found. Run <code>make build</code> then open this canvas again.</div>
</div>
<div class="Box-footer d-flex flex-items-center flex-justify-between" x-show="!loadingDefinitions && definitionsPaged.totalPages > 1">
<button class="btn btn-sm" :disabled="!definitionsPaged.hasPreviousPage" @click="loadDefinitionPage(definitionsPaged.page - 1)">Previous</button>
Expand Down Expand Up @@ -106,9 +100,7 @@ <h2 class="Box-title">Workflow runs <span class="Counter" x-text="runsPaged.tota
</div>
</div>
</template>
<div class="Box-row color-fg-muted text-center py-3" x-show="!loadingRuns && runsPaged.totalItems === 0">
No runs found. Run <code>gh aw logs</code> to check availability.
</div>
<div class="Box-row color-fg-muted text-center py-3" x-show="!loadingRuns && runsPaged.totalItems === 0">No runs found. Run <code>gh aw logs</code> to check availability.</div>
</div>
<div class="Box-footer d-flex flex-items-center flex-justify-between" x-show="!loadingRuns && runsPaged.totalPages > 1">
<button class="btn btn-sm" :disabled="!runsPaged.hasPreviousPage" @click="loadRunPage(runsPaged.page - 1)">Previous</button>
Expand Down
24 changes: 18 additions & 6 deletions pkg/linters/hardcodedfilepath/hardcodedfilepath.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"go/ast"
"go/token"
"go/types"
"regexp"
"strings"
"unicode/utf8"

Expand Down Expand Up @@ -83,13 +84,24 @@ func isPathLike(val string) bool {
return false
}

// hasFormatVerb reports whether val contains common fmt format verbs. Strings
// with verbs are format templates, not standalone paths, so they are excluded.
// fmtDirectivePattern matches a Go fmt directive of the form:
//
// %[flags][width][.precision][argindex]verb
//
// where width and precision each allow an optional explicit argument index
// before "*" (e.g. [3]*), and verb may also be preceded by an explicit
// argument index (e.g. [1]x). This covers simple forms like %s, indexed
// forms like %[1]s, and the full indexed form %[3]*.[2]*[1]x.
var fmtDirectivePattern = regexp.MustCompile(`%[#0+\- ]*(?:(?:\[[0-9]+\])?\*|\d+)?(?:\.(?:(?:\[[0-9]+\])?\*|\d+))?(?:\[[0-9]+\])?[bcdeEfFgGopqstTUvwxX]`)

// hasFormatVerb reports whether val contains fmt-style format directives.
// Strings with directives are format templates, not standalone paths, so they
// are excluded. Escaped percent pairs (%%) are ignored.
func hasFormatVerb(val string) bool {

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.

[/grill-with-docs] Naming inconsistency: the companion variable is fmtDirectivePattern and the PR description/comments now use "directives" throughout, but the function is still called hasFormatVerb. This mixed vocabulary makes it harder to grep and reason about the code.

💡 Suggested rename

Rename to align with the updated terminology:

// hasFmtDirective reports whether val contains fmt-style format directives.
func hasFmtDirective(val string) bool {

Update all call sites accordingly.

@copilot please address this.

return strings.ContainsAny(val, "%") &&
(strings.Contains(val, "%s") || strings.Contains(val, "%d") ||
strings.Contains(val, "%v") || strings.Contains(val, "%q") ||
strings.Contains(val, "%w") || strings.Contains(val, "%f"))
if !strings.Contains(val, "%") {
return false
}
return fmtDirectivePattern.MatchString(strings.ReplaceAll(val, "%%", ""))
}

// unquoteStringLit returns the raw string value of a Go string literal token,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ func okFormatVerb() string {
return fmt.Sprintf("/tmp/gh-aw/runs/%s/output.json", "run-id")
}

// ok: path template literal with %x should be treated as a format template.

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.

[/tdd] Good coverage for %x and %%, but the new regex also handles more complex forms (flags, width, precision, indexed arguments) that have no test fixtures. Without these, a regression in the regex could go undetected.

💡 Suggested additional fixtures
// ok: path with flags+width format directive should be treated as a template.
func okFlagWidthTemplateLiteral() string {
	return "/tmp/gh-aw/%-10s/output.log"
}

// ok: path with indexed argument should be treated as a template.
func okIndexedArgTemplateLiteral() string {
	return "/tmp/gh-aw/%[1]s/output.log"
}

// ok: path with precision format should be treated as a template.
func okPrecisionTemplateLiteral() string {
	return "/tmp/gh-aw/%8.2f/output.log"
}

These mirror real-world format strings and lock in the regex behaviour for the full directive grammar.

@copilot please address this.

func okHexTemplateLiteral() string {
return "/tmp/gh-aw/%x.tmp"

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.

Exclusion is now broader than verb-in-path actually implies: the old allow-list (%s, %d, %v, %q, %w, %f) was deliberately narrow because those verbs almost exclusively appear inside format strings. The new regex adds %b, %c, %o, %p, %e, %g, %t, %T, %U, etc. — verbs that can plausibly appear literally in filenames or shell glob patterns rather than as format directives.

💡 Concern and tradeoff

A path like /tmp/data/%b-backup.tar or /tmp/cache/%c.dat would now be silently excluded even if %b or %c are literally part of the filename and the string is never passed to fmt.Sprintf. The linter has no way to distinguish between these cases without tracking usage context.

This is an inherent limitation of any string-literal heuristic, but widening the set increases the false-negative surface. Consider documenting this tradeoff in the function comment — the existing comment says "Strings with directives are format templates" but that is only true by assumption, not by proof from AST context.

Suggestion: Acknowledge in the comment that the filter is a heuristic and over-exclusion is a known tradeoff, so reviewers understand the guarantee is approximate:

// hasFormatVerb reports whether val contains fmt-style format directives.
// This is a heuristic: any string matching a directive pattern is treated
// as a format template and excluded, which may produce false negatives
// when a literal % sequence appears in an actual path (not a format arg).

}

Comment on lines +73 to +77
// bad: escaped %% is not a format verb and should still be reported.
func badEscapedPercentPath() string {
return "/tmp/gh-aw/100%%-done.log" // want `hard-coded file path.*consider extracting`
}

// ok: path template with indexed width/precision/value directive (e.g. %[3]*.[2]*[1]x).
func okIndexedArgDirective() string {
return "/tmp/gh-aw/%[3]*.[2]*[1]x"
}

// ok: very short path segment (no trailing slash after prefix).
func okShortSegment() string {
return ".github"
Expand Down