Repository navigation
[typist] 🔤 Typist: Go Type Consistency Analysis #67180
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-10-10T11:38:26.017Z.
|
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 roughly 1,493 non-test
.gofiles underpkg/(~1,436 top-level type definitions) for duplicated type definitions and untyped (interface{}/any) usage. The good news first:interface{}is essentially gone from production code — the only 9 hits are intentional "bad pattern" fixtures insidepkg/linters/*/testdata, used by this repo's own custom linters to test themselves.anyis used ~2,900 times, and the overwhelming majority of that is legitimate (generics likefunc f[T any], and deliberately dynamic YAML/JSON frontmatter fields that are documented as "too dynamic to type").Digging past that, though, turned up a handful of worthwhile findings. On the duplication side,
RunOptionsandRunWorkflowOptionsinpkg/clishare 8 of 10 fields verbatim and could share a common embedded struct, andGatewayServerMetrics/GatewayToolMetricsinpkg/cli/gateway_logs_types.gore-spell a metrics shape the team already consolidated elsewhere intoMCPServerStatsBase(the existing code comment literally describes fixing this exact kind of drift) — this one's a straggler that didn't get the memo. On the untyped side, the clearest win isTarget anyrepeated 5 times inpkg/workflow/safe_outputs_azure_devops.go, even though every otherTargetfield in the same package (CreateCheckRun.Target,UpdateDiscussions.Target) is a plainstring, and tests confirm it's always a string at runtime — same story forHeaders anyvs.Headers map[string]stringused elsewhere for the identical concept.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
struct/interfacedeclarations in non-test.gofiles underpkg/)Cluster 1:
RunOptionsvsRunWorkflowOptionsType: Near duplicate
Occurrences: 2
Impact: High — same package, 80% field overlap
Locations:
pkg/cli/run_workflow_execution.go:31—type RunOptions struct {...}pkg/cli/run_interactive.go:366—type RunWorkflowOptions struct {...}Definition Comparison:
8 of
RunWorkflowOptions' 10 fields (Verbose,EngineOverride,RepoOverride,RefOverride,AutoMergePRs,Push,DryRun,Approve) are identical name+type matches withRunOptions.Recommendation:
WorkflowRunFlagsstruct holding the 8 common fields and embed it in bothRunOptionsandRunWorkflowOptions.--timeout) only requires touching one struct instead of two.Cluster 2: MCP/Gateway server & tool call metrics
Type: Near duplicate
Occurrences: 5 related types
Impact: High — the codebase already solved this once and left one case unconsolidated
Locations:
pkg/cli/gateway_logs_types.go:85—type GatewayServerMetrics struct {...}pkg/cli/gateway_logs_types.go:97—type GatewayToolMetrics struct {...}pkg/cli/logs_models.go:218—type MCPServerStatsBase struct {...}(the shared base others already embed)pkg/cli/audit_report.go:254—type MCPServerStats struct { MCPServerStatsBase; ... }pkg/cli/audit_expanded.go:94—type MCPServerHealthDetail struct { MCPServerStatsBase; ... }Definition Comparison:
Recommendation:
MCPServerStatsandMCPServerHealthDetailalready embedMCPServerStatsBaseto avoid exactly this drift —GatewayServerMetrics(live gateway-log parsing path) is the one straggler left outside that hierarchy.MCPServerStatsBaseinGatewayServerMetrics(keeping its gateway-specific fields likeTotalDuration/FilteredCount/GuardPolicyBlockedalongside it).Cluster 3: WorkQueue
Assignment/AssignmentClaimvs CLI "receipt" typesType: Semantic duplicate
Occurrences: 4
Impact: Medium
Locations:
pkg/workqueue/types.go:218—type Assignment struct {DispatchID, RequestID, CommitID, PolicyEpoch, Pool, WorkerProfile string; Claims []AssignmentClaim}pkg/workqueue/types.go:229—type AssignmentClaim struct {Handle, ClaimID, WorkID, Work, ResultRefs}pkg/cli/logs_work_queue_current.go:30—type WorkQueueAssignmentReceipt struct {DispatchID, RequestID, CommitID, State string; Released bool; Claims []WorkQueueClaimReceipt}pkg/cli/logs_work_queue_current.go:39—type WorkQueueClaimReceipt struct {Handle, ClaimID, WorkID, State, Barrier}pkg/cli's "receipt" types hand-duplicate ~60-70% of the field names frompkg/workqueue's coreAssignment/AssignmentClaimtypes instead of projecting from them.Recommendation:
ToReceipt()method onworkqueue.Assignment/AssignmentClaim, or factor out a shared identity sub-struct (DispatchID/RequestID/CommitID/Handle/ClaimID/WorkID), instead of re-declaring the field names by hand inpkg/cli.Cluster 4: Domain allow/block list duplication outside
DomainBucketsType: Semantic duplicate
Occurrences: 3
Impact: Medium
Locations:
pkg/cli/domain_buckets.go:13—type DomainBuckets struct {AllowedDomains []string; BlockedDomains []string}(designed to be embedded)pkg/cli/domains_command.go:27—type WorkflowDomainsDetail struct {Workflow, Engine string; AllowedDomains, BlockedDomains []string}pkg/cli/access_log.go:44—type domainAnalysisWire struct {...; AllowedDomains, BlockedDomains []string}Recommendation:
DomainBucketsalready exists for exactly this purpose and is embedded elsewhere viaAnalysisBase. Embed it inWorkflowDomainsDetailtoo.domainAnalysisWirecase is a JSON-compat shim that must preserve specific legacy keys — that one is a reasonable, already-understood exception and lower priority.Cluster 5: Per-tool "Finding" wrapper structs (scanner integrations)
Type: Semantic duplicate
Occurrences: 5
Impact: Low-Medium — largely justified, worth a lighter-touch fix
Locations:
pkg/scanfindings/scanfindings.go:107—type Finding struct {RuleID, Severity, Message, File, Line, Column, Context}(the shared normalized target)pkg/cli/zizmor.go:27—type zizmorFinding struct {...}pkg/cli/poutine.go:26—type poutineFinding struct {...}pkg/cli/grype.go:54—type grypeFinding struct {...}pkg/cli/runner_guard.go:23—type runnerGuardFinding struct {...}Each
*Findingstruct mirrors a distinct third-party tool's own JSON output schema (zizmor/poutine/grype/runner-guard), so merging them into one type isn't appropriate — but all four get hand-converted intoscanfindings.Findingwith near-identicalRuleID/Severity/File/Linemapping code.Recommendation:
rawScanFindinginterface) so the four conversions toscanfindings.Findingstop being copy-pasted independently.AuditFinding(pkg/cli/audit_report.go:79) andSecurityFinding(pkg/workflow/markdown_security_scanner.go:63) are further semantic cousins of the same "severity + message + location" shape worth folding in if this is tackled.Cluster 6:
ProgressBar/SpinnerWrapper(native vs. WASM) — not actionableType: Exact duplicate (name only)
Occurrences: 4
Impact: None — false positive
Locations:
pkg/console/progress.go:31,pkg/console/progress_wasm.go:9,pkg/console/spinner.go:92,pkg/console/spinner_wasm.go:12These are same-named types split across Go build tags (native vs.
GOOS=jsWASM stub) — the standard, intentional pattern already documented in this repo'sdeveloper-code-organizationskill. No change needed.Untyped Usages
Summary Statistics
interface{}usages in productionpkg/code: 0 (9 raw matches, all insidepkg/linters/*/testdatafixture files that intentionally demonstrate the anti-pattern for the custom linters' own golden tests)anyusages: ~2,900 across 565 files — the large majority are legitimate (Go generics constraints, and dynamic YAML/JSON frontmatter fields the authors have explicitly commented as "too dynamic to type")const X = <literal>declarations (mostly local, test-scoped constants); a handful of production, semantically-significant numeric constants stood outCategory 1:
anyfields that are actually always one concrete typeImpact: High — these aren't "genuinely dynamic," they're just inconsistent with identically-named fields elsewhere in the same codebase
Example 1:
Target anyin Azure DevOps safe-output configspkg/workflow/safe_outputs_azure_devops.go:40,52,57,64,70(UpdateWorkItemConfig,CommentOnWorkItemConfig,AssignWorkItemConfig,LinkWorkItemsConfig,UploadWorkItemAttachmentConfig)Target anywith tagyaml:"target,omitempty", checked only viatarget != niland consumed withfmt.Sprintf("%v", target)Targetfield in the same package is a plainstring— e.g.CreateCheckRunConfig.Target,UpdateDiscussions.Target,assign_milestone.go'sconfig.Target— andsafe_outputs_azure_devops_test.go:74assertsconfig.UpdateWorkItems.Targetequals the string"42".!= nilguards changed to!= "".Targetfield inpkg/workflow; removes a pointlessany→stringround trip through%v.Example 2:
Headers anyvs.Headers map[string]stringpkg/workflow/frontmatter_types.go:252,289,pkg/parser/import_observability.go:18Headers anywith tagjson:"headers,omitempty"pkg/workflow/tools_parser.go:717buildsconfig.Headers = make(map[string]string)for the equivalent MCP-server headers concept, andtools_types.go/observability_otlp.goconsume headers as eithermap[string]stringorstringelsewhere in the same package — never as arbitrary nested data.Headers map[string]string(matchingpkg/workflow/tools_types.go's existing header config).Category 2: Same untyped shape copy-pasted across structs (should be a named type)
Impact: Medium — not wrong, but duplicated
map[string]any/[]anyliterals hide that these are the same conceptExample:
GuardPolicies map[string]anypkg/workflow/mcp_renderer_types.go:21,94,131,pkg/workflow/mcp_config_types.go:54,pkg/workflow/tools_types.go:530map[string]anyGuardPolicies GuardPolicyMapeverywhere instead of re-typing the map literal.Example:
[]anyfor GitHub Actions step passthroughpkg/workflow/frontmatter_types.go:413-416(PreSteps,Steps,PreAgentSteps,PostSteps),pkg/workflow/threat_detection_config.go:12-13,pkg/workflow/safe_jobs.go:25,pkg/workflow/safe_outputs_config_types.go:137uses:/run:/with:/etc.), confirmed bycompiler_main_job_helpers.go'sextractRunScriptsFromSectionYAML(data.PreSteps, ...), which re-serializes them back to YAML rather than inspecting fields.type RawActionSteps []any, used consistently instead of eight independent[]anyfields.Category 3: Untyped numeric/semantic constants
Impact: Low-Medium — mostly clear from context already, but a few lack units at a glance
Examples:
Most of these already have self-documenting names (
MaxParseBytes,DefaultMaxLines), so the risk is low — but introducing small semantic types would remove any residual ambiguity and is a very cheap, mechanical change if the team wants it:Recommendation: Low priority relative to Categories 1-2. Only worth doing opportunistically (e.g. next time one of these files is touched for another reason), not as a dedicated pass.
🎯 What Should We Do About This?
Here's a suggested action plan, prioritized by impact and how cheap each fix is:
Priority 1: Quick, high-confidence fixes
Target any→Target stringinpkg/workflow/safe_outputs_azure_devops.go(5 fields, 1 file, tests already prove it's always a string)Headers any→Headers map[string]stringinpkg/workflow/frontmatter_types.goandpkg/parser/import_observability.goWorkflowRunFlagsstruct forRunOptions/RunWorkflowOptionsinpkg/cliEstimated effort: ~3-4 hours total
Impact: High — removes real type-assertion risk and finishes work the codebase already started elsewhere
Priority 2: Finish an existing consolidation
MCPServerStatsBaseinGatewayServerMetrics(pkg/cli/gateway_logs_types.go) to close the gap the team's own comment already flagsEstimated effort: 2-3 hours
Impact: High — architectural consistency across the MCP metrics surface
Priority 3: Medium-value cleanups
ToReceipt()onworkqueue.Assignment/AssignmentClaiminstead of hand-duplicating fields inpkg/cli's work-queue receipt typesDomainBucketsinWorkflowDomainsDetailGuardPolicyMapandRawActionStepsnamed type aliases for the repeatedmap[string]any/[]anyshapesEstimated effort: ~5-6 hours total
Impact: Medium — clarity and drift-prevention, not correctness
Priority 4: Opportunistic only
ByteSize, etc.) — only worth doing when already touching those files for another reason.Implementation Checklist
Target any→stringin Azure DevOps safe-output configsHeaders any→map[string]stringin frontmatter/observability typesWorkflowRunFlagsstruct forRunOptions/RunWorkflowOptionsMCPServerStatsBaseinGatewayServerMetricsToReceipt()projection on workqueueAssignment/AssignmentClaimDomainBucketsinWorkflowDomainsDetailGuardPolicyMap/RawActionStepsnamed type aliasesProgressBar/SpinnerWrapperduplication alone (intentional)Analysis Metadata
.gofiles underpkg/All reactions