[typist] Typist: Go type consistency findings in pkg/ #61120
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-09-16T11:43:26.209Z.
|
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 · scope: non-test
.gofiles underpkg/(~1,374 files) · run §34963240581Executive Summary
I scanned roughly 1,230 top-level struct/interface definitions and ~2,900 weakly-typed usages (
interface{}/anyin signatures, struct fields, and maps, plus untyped constants) acrosspkg/. The good news: most of the "duplication" I found is either intentional (platform build-tag variants, a well-factoredBaseMCPServerConfigembedding pattern) or low-risk. The most actionable finding is a family of hand-copied "wire" schema structs inpkg/cli/mcp_schema.gothat duplicate the shape ofMarshalJSON/UnmarshalJSONanonymous structs living in four other files — including one byte-for-byte exact duplicate (domainAnalysisWire/domainAnalysisWireSchema). These will silently drift the next time someone edits a marshal method without remembering to update the schema twin.On the untyped-usage side, the standout is
pkg/workflow's step-handling code:[]anystep slices get round-tripped throughSliceToSteps/StepsToSliceat 8 separate call sites inworkflow_builder_steps.go, even though a typed*WorkflowStepalready exists and is the type actually operated on in between. There's also a 3-level nestedmap[string]anywalk for model pricing (providers.<provider>.models.<model>.cost.*) whose exact shape is already documented in a comment — a clear candidate for concrete types.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
pkg/)Cluster 1:
domainAnalysisWireexact duplicateKind: Exact · Impact: High
Locations:
pkg/cli/access_log.go:44—type domainAnalysisWire struct { TotalRequests int; AllowedCount int; BlockedCount int; AllowedDomains []string; BlockedDomains []string }pkg/cli/mcp_schema.go:270—type domainAnalysisWireSchema struct { ... }(identical fields/tags)Comparison: Byte-for-byte identical.
domainAnalysisWireis the realMarshalJSONshadow struct forDomainAnalysis;domainAnalysisWireSchemais a hand-copied twin created only sojsonschema-goreflection produces a matching shape.Recommendation: Delete
domainAnalysisWireSchemaand generate the schema fromdomainAnalysisWiredirectly. Effort: ~30 min. Benefit: removes a silent-drift trap.Cluster 2: Wire-schema shadow structs mirroring
MarshalJSONanonymous typesKind: Near · Impact: High
Locations:
pkg/cli/mcp_schema.go:242—mcpServerHealthDetailWiremirrors the anonymous struct inMCPServerHealthDetail.MarshalJSON/UnmarshalJSON(pkg/cli/audit_expanded.go:101,125)pkg/cli/mcp_schema.go:253—mcpServerCrossRunHealthWiremirrorsMCPServerCrossRunHealth.MarshalJSON/UnmarshalJSON(pkg/cli/audit_cross_run.go:95,117)pkg/cli/mcp_schema.go:263—mcpFailureSummaryWiremirrorsMCPFailureSummary.MarshalJSON(pkg/cli/logs_models.go:226)pkg/cli/mcp_schema.go:234—toolUsageSummaryWiremirrorsToolUsageSummary.MarshalJSON(pkg/cli/logs_report_tools.go:56)Comparison: Four named "Wire" structs in
mcp_schema.go, each hand-duplicating the field set of an anonymous struct literal embedded in aMarshalJSON/UnmarshalJSONmethod on an unrelated type in a different file. All four pairs match field-for-field today, but nothing enforces that they stay in sync.Recommendation: Factor each anonymous marshal struct into a named, exported wire type in its home file; have
mcp_schema.goreference that single definition instead of re-declaring it. Effort: ~2-3 hours for all four. Benefit: one source of truth per wire shape.Cluster 3:
MCPToolCallvscachedLogsJSONLMCPToolCallKind: Near · Impact: Medium
Locations:
pkg/cli/audit_report.go:227—MCPToolCall(11 fields incl.Error)pkg/cli/logs_cached_json.go:72—cachedLogsJSONLMCPToolCall(same 10 fields, noError)Recommendation: Have
cachedLogsJSONLMCPToolCallembed/convert fromMCPToolCallinstead of re-declaring the shared fields.Cluster 4:
MCPToolUsageDatavsMCPToolUsageSummaryKind: Near · Impact: Medium
Locations:
pkg/cli/audit_report.go:194— used inAuditData.MCPToolUsage, addsGuardPolicySummarypkg/cli/logs_models.go:254— used inLogsData.MCPToolUsageRecommendation: Merge into one type (add
GuardPolicySummaryas optional onMCPToolUsageSummary) and reuse it in both containers.Cluster 5:
RepeatOptionsvsPollOptionsKind: Semantic · Impact: Medium
Locations:
pkg/cli/retry.go:20,pkg/cli/signal_aware_poll.go:35Comparison: Two independently-implemented "run a function repeatedly with context/signal awareness + progress messaging" option structs — one count-based, one interval/timeout-based.
Recommendation: Consider a shared loop-runner abstraction with count vs. interval/timeout modes, or document why both exist.
Cluster 6: Parallel comparison/diff subsystems (
audit_comparison.govsaudit_diff.go)Kind: Semantic · Impact: Medium
Locations:
AuditComparisonData/Baseline/Delta(audit_comparison.go) vsAuditDiff/FirewallDiff/MCPToolsDiff/ToolCallsDiff/RunMetricsDiff/GitHubRateLimitDiff(audit_diff.go)Comparison: Both model deltas between two runs (domains, MCP tools, tool calls, rate limits), for two different UX flows (implicit rolling-baseline comparison vs. explicit two-run diff), but each declares its own
IntDelta/StringDeltaprimitives.Recommendation: Extract one shared generic delta helper (
NumericDelta[T],StringDelta) used by both; keep the feature-level containers separate since the UX flows genuinely differ.Lower-priority / informational clusters
ValidationErrorname collision (pkg/parser,pkg/validationerror,pkg/workflow.WorkflowValidationError) — all three deliberately embed the sharedvalidationerror.Payloadbase; this is well-factored, just confusing to search for. No fix needed beyond a doc comment.BaseMCPServerConfigfamily (pkg/types/mcp.go, 126 references across 22 files) —MCPServerConfigandRegistryMCPServerConfigalready correctly embed the shared base.cli.VSCodeMCPServer/MCPConfigmodel an external file format and are reasonably kept separate.ToolUsageInfovsToolUsageStatsBase/ToolUsageSummary(pkg/cli) — overlapping stats fields, not currently embedded; low priority.SpinnerWrapper/ProgressBarbuild-tag duplicates (pkg/console) — same name declared under mutually-exclusive Go build tags (default vs. wasm). Intentional platform abstraction, no action needed.Untyped Usages
Summary Statistics (approximate, non-test files under
pkg/)interface{}/anyin function signatures: ~125interface{}/anyin struct fields: ~90map[string]interface{}/map[string]any: ~2,771The
map[string]anycount is large mostly because of legitimate dynamic-JSON handling (GitHub API responses, MCP tool arguments); the findings below are the subset where a concrete type is realistically feasible.Category 1:
[]anystep round-tripping (pkg/workflow)Impact: High
pkg/workflow/step_types.go:234,261—SliceToSteps(steps []any) ([]*WorkflowStep, error)/StepsToSlice(steps []*WorkflowStep) []any. Called in matched pairs 8 times inpkg/workflow/workflow_builder_steps.go(e.g. lines 23/33, 45/55, 69/79, 121/129, 142/150, 182/190, 202/210, 242/250, 263/271) — each site converts[]any → []*WorkflowStep → []anyjust to mutate steps in place, even though the typedWorkflowStepis what's actually operated on in between.[]*WorkflowSteponce parsed from frontmatter; serialize to[]anyonly at the final YAML-emission boundary.pkg/workflow/frontmatter_types.go:412-415—PreSteps,Steps,PreAgentSteps,PostStepsare all[]any, immediately fed throughSliceToStepsat nearly every consumer.[]*WorkflowStepdirectly; decodemap[string]any → WorkflowSteponce during frontmatter parsing.pkg/workflow/known_action_credentials.go:120—DetectKnownCredentialLeakingActions(steps []any)hand-rollsstep.(map[string]any)thenstepMap["uses"].(string), duplicating whatWorkflowStep.Uses/.Withalready provide.func DetectKnownCredentialLeakingActions(steps []*WorkflowStep) map[string]struct{}; callSliceToStepsonce at the call site.Category 2: Nested
map[string]anyfor model pricingImpact: High
pkg/workflow/compiler_model_pricing.go:124-207andpkg/workflow/awf_config.go:198(Providers map[string]any) — both walk a 3-level nested shape (providers.<provider>.models.<model>.cost.{input,output,cache_read,cache_write,reasoning}) via chained.(map[string]any)assertions. The shape is already documented in a comment onawf_config.go:196-197, and the same chain repeats inawf_config_build.go:519-521and several tests.ProviderPricing,ModelPricing,CostEntrystructs and drop the assertion chains.Category 3: Untyped constants (units hidden in comments, not types)
Impact: Low–Medium
pkg/workflow/cache_integrity.go:17—const defaultCacheIntegrityLevel = "none"duplicates the already-typed enum valueGitHubIntegrityNone GitHubIntegrityLevel = "none"(pkg/workflow/tools_types.go:300) as a separate untyped string. Fix: referenceGitHubIntegrityNonedirectly.pkg/constants/job_constants.go:310-311—DefaultRateLimitMax = 5,DefaultRateLimitWindow = 60(minutes, per comment only).pkg/cli/run_workflow_execution.go:25—workflowCompletionWaitTimeoutMinutes = 6 * 60, an untyped int passed as minutes next to another constant in the same file that correctly usestime.Duration.pkg/workflow/publish_code_coverage.go:18—defaultCodeCoverageWaitForProcessingTimeout = 160(seconds, per comment only), where sibling timeouts elsewhere in the package already usetime.Duration.time.Durationwhere the value is a time span, or introduce a named unit type (type Minutes int,type RetryCount int) so units live in the type system instead of a comment.Category 4: Union-typed MCP tool arguments
Impact: Low
pkg/cli/mcp_tools_privileged.go:458-459—RunID any,RunIDOrURL anyintentionally accept either a JSON string or number.json.Unmarshaler(e.g.type RunIDOrURL stringwith custom unmarshal) would keep the union explicit withoutany.Refactoring Recommendations
Priority 1 — Fix the wire-schema duplication (Clusters 1 & 2)
domainAnalysisWireSchema; generate its schema fromdomainAnalysisWire.mcpServerHealthDetailWire,mcpServerCrossRunHealthWire,mcpFailureSummaryWire,toolUsageSummaryWireto live next to (and be reused by) theMarshalJSONmethods they mirror.Effort: ~3-4 hours · Benefit: eliminates the single highest-risk drift trap found.
Priority 2 — Type the workflow step pipeline (
[]any→*WorkflowStep)FrontmatterSteps-family fields to[]*WorkflowStep.SliceToSteps/StepsToSliceround-trips inworkflow_builder_steps.go; operate on[]*WorkflowStepthroughout.DetectKnownCredentialLeakingActionsto take[]*WorkflowStep.Effort: ~4-6 hours (touches core workflow compilation — needs full test suite run) · Benefit: removes ~10 type-assertion sites and a class of possible panics.
Priority 3 — Type the model-pricing map and fix unit-less constants
ProviderPricing/ModelPricing/CostEntryforcompiler_model_pricing.goandawf_config.go.time.Durationor named unit types.Effort: ~2-3 hours · Benefit: clearer intent, no more comment-only units.
Implementation Checklist
domainAnalysisWireSchema, point schema generation atdomainAnalysisWiremcp_schema.go"Wire" structs into their source filesPreSteps/Steps/PreAgentSteps/PostStepsas[]*WorkflowStepSliceToSteps/StepsToSliceround-trips inworkflow_builder_steps.goProviderPricing/ModelPricing/CostEntrytypes for model pricing mapstime.Durationor named unit typesgo test ./pkg/...)Analysis Metadata
.gofiles underpkg/(~1,374 files)grep/rgpattern search + manual verification via file reads (no code execution)References:
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
api.anthropic.comTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.
All reactions