[typist] Typist: Go Type Consistency Analysis — duplicate types & untyped usages in pkg/ #56632
Closed
Replies: 2 comments
|
This discussion was automatically closed because it expired on 2026-08-29T15:43:36.107Z.
|
0 replies
|
🤖 Smoke test bot stopping by! This typist report is looking sharp — keep those types consistent. Beep boop, all systems nominal! 🎉 Warning Firewall blocked 6 domainsThe following domains were blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "accounts.google.com"
- "android.clients.google.com"
- "clients2.google.com"
- "contentautofill.googleapis.com"
- "www.google.com"
- "www.gstatic.com"See Network Configuration for more information.
|
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
🔤 Typist - Go Type Consistency Analysis
Analysis of repository: github/gh-aw
Executive Summary
I scanned all non-test
.gofiles underpkg/(1,326 files, ~1,150 struct/interface type definitions), using Serena for semantic lookups plus targeted structural analysis of the two largest packages,pkg/workflow(~700 files) andpkg/cli(~440 files).The good news first: this codebase already practices strong typing well in the places it matters most. There is essentially zero raw
interface{}left in production code (one stray mention survives only in a comment),map[string]anyis used consistently and deliberately for the one place it's actually appropriate — passing through arbitrary user-authored YAML/JSON — and a sharedpkg/types.BaseMCPServerConfigalready prevents what would otherwise be MCP-config duplication acrossparserandworkflow. The team has clearly already run consolidation passes in a few areas (e.g.ToolUsageStatsBase,DiffEntryBaseinpkg/cli).That said, I found 11 duplicate/near-duplicate type clusters and about a dozen untyped-usage spots worth a look. The standout:
SafeOutputTargetConfig(thetarget/target-repo/allowed-repostrio used by every safe-output config) is already correctly embedded in 13 configs — but 11 other safe-output configs hand-copied the same three fields instead of embedding it, so the shared type exists and is just half-adopted. That's a low-risk, high-value, mechanical fix. There's also live drift already visible inpkg/cli:AuditDataandRunAnalysisduplicate the same ~12-field analysis payload, and one of them renamed a shared field (FirewallTokenUsagevsTokenUsage) in the process.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
pkg/workflow,pkg/cli, plus a full-repo sweep of the rest)Cluster 1:
SafeOutputTargetConfig— half-adopted shared typeType: Exact duplicate (11 manual copies) alongside 13 correct embeddings
Impact: High — the fix is purely mechanical and the shared type already exists
The canonical type is
pkg/workflow/safe_outputs_parser.go:9:Correctly embedded (
SafeOutputTargetConfigyaml:",inline"``) in 13 configs:assign_milestone.go:12, `mark_pull_request_as_ready_for_review.go:12`, `hide_comment.go:12`, `set_issue_field.go:10`, `add_reviewer.go:12`, `dismiss_pull_request_review.go:10`, `replace_label.go:21`, `set_issue_type.go:12`, `update_entity_helpers.go:95`, `submit_pr_review.go:18`, `resolve_pr_review_thread.go:14`, `link_sub_issue.go:12`, `reply_to_pr_review_comment.go:13`.Manually re-declared (same 3 fields, same yaml tags, copy-pasted instead of embedded) in 11 more:
create_issue.go:21-22,add_comment.go:18-20,dispatch_workflow.go:17,create_pr_review_comment.go:15,create_agent_session.go:13,update_project.go:28,create_pull_request.go:96,push_to_pull_request_branch.go:24,create_code_scanning_alert.go:18,create_discussion.go:21,comment_memory.go:10-12.Recommendation: Swap the 11 manual declarations for the embedded form. YAML output shape is unchanged (inline embedding serializes identically), so this is a safe, mechanical refactor.
Estimated effort: 1-2 hours
Benefits: Removes ~33 duplicated lines, closes the gap so future fields on
SafeOutputTargetConfig(e.g. a new target mode) propagate everywhere automatically.Cluster 2:
AuditDatavsRunAnalysis— parallel single-run analysis graphs, with live driftType: Near duplicate
Impact: High — a field has already drifted apart under two names
pkg/cli/audit_report.go:29(AuditData) andpkg/cli/logs_models.go:247(RunAnalysis, embedded intoRunSummaryatlogs_models.go:282andDownloadResultatlogs_models.go:292) both wrap the same ~12 nested analysis types with identical field name/type/json-tag:TaskDomain,BehaviorFingerprint,AgenticAssessments,MissingTools,MissingData,Noops,MCPFailures,SkillActivations,MCPToolUsage,GitHubRateLimitUsage,FirewallAnalysis,RedactedDomainsAnalysis.They diverge only in the "header" (
AuditData.Overview OverviewDatavsRunAnalysis.Run WorkflowRun) and in report-only extras. Evidence of drift:AuditData.FirewallTokenUsage *TokenUsageSummary(jsonfirewall_token_usage) is the same type asRunAnalysis.TokenUsage *TokenUsageSummary(jsontoken_usage_summary) but under a different field/JSON name.Recommendation: Extract the 12 shared fields into a
RunAnalysisCorestruct embedded by both, or haveAuditDataembedRunAnalysisdirectly and add only its report-specific fields.Estimated effort: 2-3 hours
Benefits: Single source of truth for "what a run's analysis contains", eliminates the risk of the two views silently diverging further.
Cluster 3: "Workflow run" modeled 4 different ways
Type: Mixed (1 exact, rest semantic view-projections)
Impact: Medium
pkg/cli/logs_models.go:60WorkflowRun— the full internal model (DatabaseID,Number,URL,Status,Conclusion,WorkflowName,WorkflowPath, timestamps,Event,HeadBranch,HeadSha, ...).pkg/cli/run_workflow_tracking.go:19WorkflowRunInfo—URL,DatabaseID,Status,Conclusion,CreatedAt: a strict, 100%-matching 5-field subset ofWorkflowRun, used only for post-trigger polling.pkg/cli/audit_report.go:94OverviewDataandpkg/cli/logs_report.go:116RunDataare legitimate view projections with different JSON key casing for different commands (auditvslogs) — not recommended for full unification, but they duplicate ~6 field declarations that a sharedRunIdentitystruct could absorb.Recommendation: Delete
WorkflowRunInfoand haverun_workflow_tracking.gouse*WorkflowRundirectly (exact duplicate, zero-risk removal). LeaveOverviewData/RunDataas separate output schemas, optionally sharing a smallRunIdentity{RunID, WorkflowName, WorkflowPath, Status, Conclusion, CreatedAt}embed.Estimated effort: 1 hour for
WorkflowRunInforemovalBenefits: One less type to keep in sync with
WorkflowRun's evolution.Cluster 4: Memory-entry family —
CacheMemoryEntry/RepoMemoryEntry/DriveMemoryEntryType: Near duplicate
Impact: Medium
All three (
pkg/workflow/cache_config.go:106,pkg/workflow/repo_memory.go:47,pkg/workflow/drive_memory_config.go:38) share an identical core —ID,Description,RestoreOnly,AllowedExtensions []string,Validation *MemoryValidationConfig— then add their own storage-specific fields.Recommendation: Extract the 5 shared fields into an embedded
MemoryEntryBase(yaml:",inline").Estimated effort: 1-2 hours
Benefits: One place to change shared memory-entry semantics instead of three.
Cluster 5:
GitHubMCPDockerOptionsvsGitHubMCPRemoteOptionsType: Near duplicate
Impact: Medium
pkg/workflow/mcp_renderer_types.go:67and:101share 8 of ~12 fields (ReadOnly,Lockdown,LockdownFromStep,GuardPoliciesFromStep,Toolsets,Features,AllowedTools,GuardPolicies), diverging only on transport-specific fields.Recommendation: Extract the 8 shared fields into an embedded
GitHubMCPCommonOptions.Estimated effort: 1 hour
Benefits: Docker/Remote transport options stay in lockstep for shared behavior (read-only mode, lockdown, tool filtering).
Cluster 6:
AwInfovsAuditEngineConfig— engine metadata reimplementedType: Near duplicate
Impact: Medium
pkg/cli/logs_models.go:373(AwInfo, the rawaw_info.jsonschema) andpkg/cli/audit_expanded.go:23(AuditEngineConfig) share 7 of 9 fields (EngineID,EngineName,Model,Version,CLIVersion,FirewallVersion,Repository) verbatim.AuditEngineConfigis a hand-built projection that already missed propagatingAwInfo'sAgentRuntimefield.Recommendation: Build
AuditEngineConfigvia an explicit constructor from*AwInfo, or embed the shared subset.Estimated effort: 1 hour
Benefits: Prevents future
AwInfofields from silently failing to reach audit reports (asAgentRuntimealready has).Cluster 7:
ToolUsageInfonever migrated to the package's own established base typeType: Near duplicate
Impact: Low-medium (easy fix, package already has the right pattern)
pkg/cli/audit_report.go:159ToolUsageInfoduplicates 3 of 4 fields (CallCount,MaxOutputSize,MaxDuration) already extracted intoToolUsageStatsBase(pkg/cli/logs_report_tools.go:14) and reused by its siblingsToolUsageSummary/MCPToolSummary.Recommendation: Embed
ToolUsageStatsBaseinToolUsageInfotoo, for consistency with the rest of the family.Estimated effort: 30 minutes
Benefits: Closes the one gap in an otherwise-completed consolidation.
Cluster 8:
graderManifestEntry— defined independently in producer and consumer packagesType: Near duplicate (cross-package)
Impact: Medium-high — this is a write/read pair that can silently drift
pkg/workflow/compiler_yaml_graders.go:67(the compiler's version, written into the manifest JSON consumed by the JS grader runtime) andpkg/cli/audit_report_graders.go:67(the CLI's version, used when reading grader results back for the audit report) both declare agraderManifestEntrystruct with overlappingID,Name,Unit,Direction,Thresholdfields — but the workflow-side struct has 8 more fields (Description,Source,Enabled,Max,Min,Digest,Run,Config) that the cli-side reader silently ignores.Recommendation: Move the shared read/write schema into
pkg/types(following the existingBaseMCPServerConfigpattern) so both packages import one canonical shape instead of maintaining two independently-evolving JSON contracts for the same file.Estimated effort: 2 hours
Benefits: A future compiler-side field addition (e.g. a new grader property) won't have to be separately re-added on the reading side to show up in audit reports.
Cluster 9:
runtimeImportReference— duplicated struct and duplicated regexType: Exact duplicate (cross-package)
Impact: Medium
pkg/parser/frontmatter_hash.go:572andpkg/workflow/runtime_import_validation.go:53each independently define:and each package separately compiles the same regex literal (
\{\{#(?:runtime-import|import)\??(?:[ \t]+|[ \t]*:[ \t]*)([^{}]+?)\}\}) to populate it —runtimeImportReferenceRein parser,runtimeImportMacroRein workflow.Recommendation: Move the struct and the regex into a shared location (
pkg/parseralready being imported bypkg/workflow, or a new small shared package) so a future change to the{{#runtime-import}}macro syntax can't be applied to one copy and missed in the other.Estimated effort: 1-2 hours
Benefits: Single source of truth for runtime-import macro syntax.
Clusters 10-11: Lower priority, noted for awareness only
Raw anytool-config wrappers:CacheMemoryToolConfig,DriveMemoryToolConfig,CommentMemoryToolConfig,RepoMemoryToolConfig(pkg/workflow/tools_types.goandrepo_memory.go:64) are all exactlystruct { Raw anyyaml:"-"}. Trivial to consolidate into oneRawPassthroughToolConfig, but low impact.AgentAPIProxyTargetConfig(pkg/workflow/sandbox.go:120) vsAWFAPITargetConfig(pkg/workflow/awf_config.go:217) share 4 fields (AuthHeader,ExtraHeaders,ExtraBodyFields,SessionId) but are a legitimate two-layer split (user-facing frontmatter schema vs. external AWF tool schema) — leave as two structs, optionally share the 4 common fields via an embed to avoid name drift.Left alone (verified, not duplicates):
pkg/console'sProgressBar/SpinnerWrapperappear twice each, but it's a(go/redacted):build wasmvs!wasmplatform split with genuinely different implementations — correct Go idiom, not a smell.SRTNetworkConfig/AWFNetworkConfigand similar sandbox pairs map to different, non-interchangeable external JSON schemas (legacy SRT is being migrated away from, seemigrateSRTToAWF).pkg/types.BaseMCPServerConfigis already a working example of the fix pattern recommended above — it's explicitly documented as being embedded by bothparser.RegistryMCPServerConfigandworkflow.MCPServerConfigto avoid exactly this kind of duplication.Untyped Usages
Summary Statistics
interface{}usages in production code: 0 (one stray mention survives only inside a comment)anyusages that cross a real API boundary (vs. legitimate passthrough): ~7 flagged belowmap[string]any/[]anypassthrough usages: thousands, and correctly left alone (see note below)Category 1:
anyfields hiding an implicit sum typeImpact: High — each is type-switched independently at multiple call sites, so a missed case fails silently
PrivateToPublicFlows any—pkg/workflow/tools_types.go:419, inGitHubToolConfig. Holds either the literal string"allow"or a[]stringof MCP server IDs, and is re-type-switched independently in three separate files:mcp_gateway_config.go:175,mcp_github_config.go:565,strict_mode_network_validation.go:235,256.Suggested fix:
with one parse function and
IsAllowAll()/ServerIDs()accessors replacing the 4 scattered assertions.Data any—pkg/workflow/safe_outputs_config_types.go:96, inSafeOutputsConfig. A 4-shape union (bool false/omitted, bool true, inline schema object, or GH Actions expression string), type-switched inpkg/workflow/safe_outputs_data_schema.go:76. Lower urgency (single call site), but a named sum type would let the switch's exhaustiveness be checked once.Category 2: Adjacent same-typed parameters (real swap risk)
Impact: High — this is a genuine "wrong argument order compiles fine" hazard
Location:
pkg/cli/mcp_tools_privileged.go:133and:118requestedTimeout(minutes) andcount(number of runs) are both bareint— nothing stops a caller from swapping them. The related constants (mcpLogsRunsPerDefaultTimeoutMinute,defaultMCPLogsTimeoutMinutes, lines 20-22) are bareinttoo.Suggested fix:
Category 3: Untyped duration/size constants (semantic clarity)
Impact: Medium — internal-only, so safe to change; the package is already inconsistent about it (some sibling files already do this correctly)
Suggested fix: multiply into
time.Durationat the declaration site (160 * time.Second,600 * time.Second,5 * time.Minute), matching the style the package already uses elsewhere — this is exactly the class of bug that forcedMaxCapabilityTTL's JSON tag to spell out_secondsin its name because the Go type alone doesn't carry the unit.Lower priority — bare byte-size constants (
defaultRepoMemoryMaxFileSize = 102400 // 100KB,defaultRepoMemoryMaxPatchSize = 10240 // 10KB,maxRepoMemoryPatchSize = 1048576 // 1MBinpkg/workflow/repo_memory.go:27-31;maxOperationalValueEvaluatorSize = 64 * 1024inpkg/workflow/graders_operational_value.go:16) are internal-only and low-risk, but atype ByteSize intwithKB/MBconstants would make the mixed style (some already multiplied, some not) consistent.Category 4: Legitimate
anyusage — no action neededThe following are correct, idiomatic uses of
anyand are called out only so they aren't mistaken for gaps in a future pass:map[string]any/[]anyacross both packages (thousands of occurrences) — this is how the compiler represents arbitrary frontmatter, generated GitHub Actionswith:blocks, and tool schemas. Changing this would require modeling the entirety of GitHub Actions YAML as Go types, which is not worth the cost.On any,RunsOn any,Imports any,Checkout any,Engine any(pkg/workflow/workflow_file.go,frontmatter_types.go,runs_on_unmarshal.go) andOn anyinpkg/cli/jsonworkflow_to_markdown.go:39/list_workflows_command.go:27— these fields can legitimately be a string, array, or object in upstream YAML; Go has no union types, soany+ a type-switch during parsing is the correct idiom.RunID/RunIDOrURL any(pkg/cli/mcp_tools_privileged.go:410-411, JSON-RPC tool args accepting string or number) andID any(pkg/cli/gateway_logs_types.go:179,190, JSON-RPC 2.0's spec'did: string|number|null).pkg/typeutil/convert.go(ParseIntValue(value any),ConvertToInt(val any), etc.) — this package's entire purpose is safe conversion of heterogeneous values;anyparameters are the point, not a gap.DoWithContext(..., response any) error(pkg/cli/update_check.go:201) mirrorsencoding/json.Unmarshal's own signature — standard Go idiom.mcpErrorRateThreshold,mcpConnectionRateThreshold,spikeDetectionMultiplier(pkg/cli/audit_cross_run.go:15-23) and similar single-use, well-commented constants — idiomatic Go, not worth wrapping.Refactoring Recommendations
Priority 1: Critical — Finish the
SafeOutputTargetConfigrolloutRecommendation: Replace the 11 hand-copied
Target/TargetRepoSlug/AllowedReposfield trios with an embeddedSafeOutputTargetConfig.Steps:
SafeOutputTargetConfigyaml:",inline"``.Target: "..."→ nested or via the embedded promoted field, which works unchanged in Go).Estimated effort: 1-2 hours
Impact: High — a shared type already exists; this closes the half-finished adoption gap.
Priority 2: High — Fix live drift in
pkg/clirun-analysis typesRecommendation: Extract
RunAnalysisCoreshared byAuditDataandRunAnalysis; renameAuditData.FirewallTokenUsageto matchRunAnalysis.TokenUsage(or vice versa) as part of the same pass.Steps:
RunAnalysisCorewith the 12 shared analysis fields.AuditDataandRunAnalysis.FirewallTokenUsage/TokenUsagenaming so both structs expose the same field/JSON name.pkg/clitests, particularly anything snapshotting audit-report JSON shape.Estimated effort: 2-3 hours
Impact: High — removes an active source of silent divergence between the audit and logs code paths.
Priority 3: Medium — Delete
WorkflowRunInfo, addPrivateToPublicFlowsPolicy, fix thetimeoutMinutes/countswap riskRecommendation: Three independent, small, high-value fixes:
pkg/cli/run_workflow_tracking.go'sWorkflowRunInfoand use*WorkflowRundirectly.PrivateToPublicFlowsPolicyinpkg/workflowand replace the 4 scattered type assertions onPrivateToPublicFlows any.pkg/cli/mcp_tools_privileged.go's timeout/count parameters distinct types so they can't be swapped.Estimated effort: 3-4 hours combined
Impact: Medium — each is small in isolation but removes a real footgun.
Priority 4: Low — Remaining consolidations
Memory-entry family,
GitHubMCPDockerOptions/RemoteOptions,AwInfo/AuditEngineConfig,ToolUsageInfo, the cross-packagegraderManifestEntryandruntimeImportReferencepairs, and the duration/byte-size constant typing — all detailed above, roughly 1-2 hours each, do independently as time allows.Implementation Checklist
SafeOutputTargetConfigin the 11 manually-duplicated safe-output configsRunAnalysisCoreshared byAuditDataandRunAnalysis; reconcile theFirewallTokenUsage/TokenUsagenamingWorkflowRunInfo, use*WorkflowRundirectly inrun_workflow_tracking.goPrivateToPublicFlows anywith a typedPrivateToPublicFlowsPolicymcp_tools_privileged.goMemoryEntryBaseforCacheMemoryEntry/RepoMemoryEntry/DriveMemoryEntryGitHubMCPCommonOptionsfor the Docker/Remote MCP option structsAuditEngineConfigfromAwInfovia a constructor (fixes the missingAgentRuntimepropagation)ToolUsageStatsBaseinToolUsageInfograderManifestEntry's shared fields intopkg/typesruntimeImportReferenceand its regex into one shared locationtime.Duration/typed unitsAnalysis Metadata
pkg/)pkg/workflowandpkg/cliAll reactions