You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I scanned all 1,258 non-test .go files under pkg/ (roughly 1,179 top-level type declarations) for two things: types that are defined more than once in slightly different shapes, and places where any/interface{} or bare untyped constants are standing in for a knowable concrete type. The good news is the codebase mostly avoids exact-name, exact-shape duplication — the few true twins are intentional (go/redacted):build wasm platform stubs. The real cost is in near-duplicates: the same field or field-group copy-pasted across many structs instead of being factored into a shared embed, and a family of six-plus independent "Finding" types (one per security-scanner integration) that each reinvent severity/location fields from scratch.
The single highest-leverage fix is mechanical: Footer *string, TargetRepoSlug/AllowedRepos, and CloseOlderKey are already copy-pasted verbatim across 10-12 safe-output config structs in pkg/workflow, even though a BaseSafeOutputConfig embed already exists and solves exactly this problem for other fields (Max, GitHubToken, Staged, ...) — it just wasn't extended to cover these three. On the untyped side, the codebase has fully migrated from interface{} to any (only 18 stray interface{} hits, mostly in linter testdata), but any itself is used ~14,000 times, with the highest-value targets being YAML-decoded config values (tools[toolName] any, needs: any) that are type-switched back into a small, already-known set of shapes at every call site — a pattern the codebase already solves correctly elsewhere (e.g. ActionMode string in pkg/workflow/action_mode.go), just not consistently.
Full Analysis Report
Duplicated Type Definitions
Summary Statistics
Total types analyzed: ~1,179 (across 1,258 files)
Duplicate clusters found: 12
Exact duplicates: 2 clusters (both intentional wasm/native build-tag twins, plus 6 already-correct type aliases)
Near duplicates: 6 clusters
Semantic duplicates: 4 clusters
Cluster 1: Security-scanner Finding/Issue records (semantic, high impact)
Definition Comparison (shape, not literal code — each tool names its fields differently for the same concepts):
type<Tool>Findingstruct {
<RuleID/Ident/Name>stringSeveritystring// or Level string — bare string per tool, no shared enum<Message/Description/Desc>stringFile, Line, Column...// location shape differs per tool
}
Recommendation:
Introduce a shared Finding{RuleID string, Severity SeverityLevel, Message string, File string, Line, Column int} type (e.g. in pkg/console or a new pkg/scanfindings), with a SeverityLevel enum shared across all 9 call sites.
Give each tool integration a small adapter that maps its native JSON shape onto the shared Finding, rather than declaring a bespoke struct.
// Identical in all 11 structs:Footer*string`yaml:"footer,omitempty"`// Controls whether AI-generated footer is added...
Recommendation:
pkg/workflow/safe_outputs_config_types.go:22 already defines BaseSafeOutputConfig{Max, GitHubToken, GitHubApp, Staged, IssueIntent, NormalizeClosingKeywords, Samples} and is embedded by these same structs — but Footer was never added to it.
Move Footer *string into BaseSafeOutputConfig and delete the 11 duplicate declarations.
Estimated effort: 1-2 hours
Benefits: single source of truth, no risk of one config type's footer behavior drifting from the others
Cluster 3: TargetRepoSlug / AllowedRepos cross-repo fields (near, high impact)
Extract a CrossRepoConfig{TargetRepoSlug string, AllowedRepos []string} embeddable struct in pkg/workflow/safe_outputs_config_types.go, alongside the existing BaseSafeOutputConfig, and embed it in the 12 structs currently redeclaring both fields.
Recommendation: Unify with a CloseOlderConfig{Enabled *string, Key string} embeddable type, mirroring the CloseEntityConfig alias pattern already used correctly in close_entity_helpers.go (see Cluster 9 below). Estimated effort: 1-2 hours
Cluster 5: GitHubMCPDockerOptions vs GitHubMCPRemoteOptions (near, medium impact)
Location: pkg/workflow/mcp_renderer_types.go:67 and :101
8 fields (ReadOnly, Lockdown, LockdownFromStep, GuardPoliciesFromStep, Toolsets, Features, AllowedTools []string, GuardPolicies map[string]any) are identical between the two structs; only the transport-specific fields differ (CustomArgs/DockerImageVersion vs AuthorizationValue).
Recommendation: Factor the 8 shared fields into a GitHubMCPCommonOptions struct embedded by both. Estimated effort: 1 hour.
Cluster 6: workflow_errors.go error type family (near, medium impact)
WorkflowValidationError, OperationError, and ConfigurationError (all in pkg/workflow/workflow_errors.go:38/98/154) share Reason, Suggestion, Timestamp plus varying identity fields (Field+location+Severity/Category vs Operation/EntityType/EntityID vs ConfigKey).
Recommendation: Share a baseError{Reason, Suggestion string; Timestamp time.Time} embed with a custom Error() per type, rather than repeating the three common fields three times. Estimated effort: 1-2 hours.
Cluster 7: LockMetadata vs AgentMetadataInfo (near, medium impact)
pkg/workflow/lock_schema.go:35 (LockMetadata) and :52 (AgentMetadataInfo) — AgentMetadataInfo's 7 fields (AgentID, AgentModel, DetectionAgentID, DetectionAgentModel, EngineBaseURLCustomized, EngineVersions, AgentImageRunner) are a verbatim subset of LockMetadata's fields, in the same file.
Recommendation: Embed AgentMetadataInfo inside LockMetadata instead of maintaining both independently. Estimated effort: < 1 hour.
Cluster 8: Ad-hoc status/severity/level enums, no shared vocabulary (semantic, medium impact)
ErrorSeverity int (pkg/workflow/error_recovery.go:23), AttributionStatus string (pkg/intent/resolver.go:7), CheckState string (pkg/cli/checks_command.go:23), OutcomeStatus string (pkg/cli/outcome_evaluation.go:12), PermissionLevel string (pkg/workflow/permissions.go:46), GitHubIntegrityLevel string (pkg/workflow/tools_types.go:287), plus the bare Severity/Level string fields in Cluster 1.
Recommendation: Not urgent to merge across domains, but a shared convention (or a common pkg/enums pattern) would stop every package from hand-rolling its own status/severity vocabulary. Lower priority than Clusters 1-4.
Cluster 9: "Metadata" name reused for 5 unrelated concepts (semantic, low impact)
Metadata (pagination, pkg/cli/mcp_registry_types.go:14), WorkflowMCPMetadata, ActionMetadata, LockMetadata, commandsHeaderMetadata — structurally unrelated, no merge needed, but the bare Metadata name in pkg/cli/mcp_registry_types.go makes grep/navigation harder. Consider renaming to RegistryPageMetadata.
Cluster 10: Six independent *Status structs (semantic, low impact)
WorkflowStatus, WorkflowFileStatus, LockFileStatus, DomainRunStatus, PRCommitStatus, GuardrailStatus — each represents a genuinely different concept (git state vs. lock file vs. CI check). No merge recommended; flagged only so a 7th isn't added without checking for overlap first.
Cluster 11: Per-tool *ToolConfig types under tools_types.go (near, low impact)
9 types (GitHubToolConfig, BoundedQueriesConfig, PlaywrightToolConfig, BashToolConfig, WebFetchToolConfig, WebSearchToolConfig, EditToolConfig, AgenticWorkflowsToolConfig, CacheMemoryToolConfig) — mostly legitimate per-tool variance, but worth a pass to confirm each needs a distinct type vs. a shared ToolConfig{Enabled bool, Args map[string]any} with tool-specific extension fields.
Cluster 12: Already-correct patterns (no action needed)
Type aliases: CloseIssuesConfig = CloseEntityConfig, ClosePullRequestsConfig = CloseEntityConfig, CloseDiscussionsConfig = CloseEntityConfig (close_entity_helpers.go:263-265), MissingDataConfig/MissingToolConfig/ReportIncompleteConfig = IssueReportingConfig (missing_issue_reporting.go:26-42) — this is the pattern Clusters 2-4 should move toward.
Build-tag twins: SpinnerWrapper (console/spinner.go vs spinner_wasm.go), ProgressBar (console/progress.go vs progress_wasm.go), RepositoryFeatures (repository_features_validation.go vs _wasm.go) — intentional (go/redacted):build js || wasm stubs, correctly duplicated for platform parity.
Untyped Usages
Summary Statistics
any usages (repo-wide, pkg/): ~14,149 (rough grep count including legitimate generic/JSON/reflection use — see notes below)
Stray interface{} usages: 18 (mostly in linter testdata — the codebase has already migrated to any)
Untyped semantic constants flagged: ~620 candidates, narrowed to the highest-impact examples below
Note: the vast majority of any usage is legitimate (JSON unmarshal targets, generic containers, reflection). The categories below are the subset judged to be a "lazy" substitute for an already-knowable concrete type.
Category 1: any in function parameters for YAML-decoded config values (high impact)
Example 1: Frontmatter scalar coercion, repeated per call site
Location: pkg/workflow/engine_config_parser.go:29 (parseMaxRunsValue), plus parseMaxAICreditsValue, parseMaxTurnsValue, parseHarnessMaxRetriesValue, parseMaxToolDenialsValue
Current: func parseMaxRunsValue(raw any) int — each function re-implements the same int/int64/uint64/float64/string type-switch
Actual usage: raw always comes from a decoded YAML frontmatter map[string]any
Suggested fix: route through the existing pkg/typeutil.ParseIntValue(raw) helper instead of re-implementing the switch per call site
Benefit: one coercion path instead of 5+ near-identical ones
Example 2: Per-tool config parsers, 10+ near-identical signatures
Location: pkg/workflow/tools_parser.go:190-689 — parseGitHubTool(val any), parseBashTool(val any), and 8+ siblings
Actual usage: val is always tools[toolName] from a decoded map[string]any; each function asserts to map[string]any or bool internally (the implicit contract is bool | map[string]any)
Suggested fix: a small ToolValue wrapper with .AsMap()/.AsBool() accessors shared by all parseXTool functions
Benefit: makes the bool-or-map contract explicit and centralizes the failure path
Suggested fix: type JobNeeds any (documented union) with a custom UnmarshalYAML that normalizes to []string once
Benefit: this exact 3-branch switch likely recurs anywhere needs: is parsed — centralizing avoids drift
Category 2: any / map[string]any struct fields with a known, finite shape (high impact)
Example 1: InputDefinition.Default
Location: pkg/types/input_definition.go:18 — Default any with a comment "Can be string, number, or boolean"
Actual usage: GetDefaultAsString() (same file, line 24) type-switches string/bool/int/int64/float64 with a silent %v fallback (lines 48-49) that can produce surprising output for unexpected types
Suggested fix: a small tagged union (DefaultValue{Str, Num, Bool, Kind}) — the comment already documents the exact 3 legal types
Benefit: removes the silent fallback path, compile-time exhaustiveness
Example 2: GuardPolicies map[string]any
Location: pkg/workflow/tools_types.go:530 — comment already enumerates known variants ("For GitHub: policies are represented via GitHubAllowOnlyPolicy...")
Suggested fix: map[string]json.RawMessage or a typed union keyed by server kind
Benefit: the code already knows the finite set of policy shapes per server; the field just doesn't reflect it
Example 3: Duplicated trial-result blobs
Location: pkg/cli/trial_types.go:9-12 and pkg/cli/trial_support.go:20-23 — SafeOutputs map[string]any, AgenticRunInfo map[string]any, AdditionalArtifacts map[string]any, declared near-identically in two files
Actual usage: SafeOutputs's shape actually mirrors the already-typed workflow.SafeOutputsConfig
Suggested fix: reuse workflow.SafeOutputsConfig instead of re-decoding into map[string]any in two parallel structs
Benefit: removes a duplicate type definition and restores type safety
Category 3: any as return type where the concrete type is already known (medium impact)
Location: pkg/parser/schema_suggestions.go:386/395/405/417 — generateStringExample(...) any, generateNumberExample(...) any, generateArrayExample(...) any, generateObjectExample(...) any always return string, float64, []any, map[string]any respectively (only the top-level dispatcher generateExampleFromSchema at line 273 genuinely needs any, since JSON-schema type is dynamic).
Suggested fix: give each helper its real concrete return type; callers currently re-assert a type that was already statically known from the function name.
Location: pkg/workflow/run_phase.go:7-11 — runPhaseAgent = "agent", runPhaseDetection = "detection", runPhaseEvals = "evals", compared with == across 6 engine files (claude_engine.go, codex_engine.go, gemini_engine.go, pi_engine.go, copilot_engine_execution.go) and written into env["GH_AW_PHASE"]
Suggested fix: type RunPhase string with typed constants — mirrors the ActionMode string pattern already used correctly one file over, in pkg/workflow/action_mode.go:16-24
Benefit: the fix pattern already exists in the same package; applying it here prevents a stray literal from silently breaking phase comparisons in 5+ files
Example 2: Default-model constants mixed into an unrelated const block
Location: pkg/constants/engine_constants.go:300-316 — CopilotBYOKDefaultModel, CodexDefaultModel, AgentDefaultModel, SonnetDefaultModel sit in the same untyped const block as ~30 *EnvVar name constants
Suggested fix: type ModelName string for the *DefaultModel group so it isn't accidentally type-compatible with unrelated env-var-name strings in the same block
Add Footer *string to BaseSafeOutputConfig in pkg/workflow/safe_outputs_config_types.go; delete the 11 duplicate declarations.
Add a new embeddable CrossRepoConfig{TargetRepoSlug, AllowedRepos} and embed it in the 12 structs that redeclare both fields.
Add a CloseOlderConfig{Enabled *string, Key string} for the 4 close-older duplicates.
Run go build ./... and the existing test suite to confirm no YAML-tag or JSON-shape regressions.
Estimated effort: 4-6 hours combined Impact: High — removes ~27 duplicate field declarations across pkg/workflow, single source of truth for the safe-output config surface
Priority 2: High — Consolidate the security-scanner Finding family (Cluster 1)
Steps:
Define a shared Finding{RuleID, Severity SeverityLevel, Message, File string, Line, Column int} and a SeverityLevel enum.
Add a mapping function per integration (zizmor, poutine, grype, runner-guard, yamllint, grant) from its native JSON shape to the shared type.
Update rendering/sorting code to consume the shared type.
Estimated effort: 4-6 hours Impact: High — one severity vocabulary instead of nine, easier to onboard a new scanner integration
Priority 3: Medium — Replace YAML-decoded any parameters with documented union types (Category 1-2)
Steps:
Route the frontmatter scalar parsers (engine_config_parser.go) through the existing pkg/typeutil.ParseIntValue helper instead of re-implementing the switch.
Introduce a ToolValue wrapper (.AsMap()/.AsBool()) for the 10+ parseXTool(val any) functions in tools_parser.go.
Apply the ActionMode-style typed-string pattern to run_phase.go's phase constants and runner_guard_activation_gate.go's Needs/JobNeeds.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
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 1,258 non-test
.gofiles underpkg/(roughly 1,179 top-level type declarations) for two things: types that are defined more than once in slightly different shapes, and places whereany/interface{}or bare untyped constants are standing in for a knowable concrete type. The good news is the codebase mostly avoids exact-name, exact-shape duplication — the few true twins are intentional(go/redacted):build wasmplatform stubs. The real cost is in near-duplicates: the same field or field-group copy-pasted across many structs instead of being factored into a shared embed, and a family of six-plus independent "Finding" types (one per security-scanner integration) that each reinvent severity/location fields from scratch.The single highest-leverage fix is mechanical:
Footer *string,TargetRepoSlug/AllowedRepos, andCloseOlderKeyare already copy-pasted verbatim across 10-12 safe-output config structs inpkg/workflow, even though aBaseSafeOutputConfigembed already exists and solves exactly this problem for other fields (Max,GitHubToken,Staged, ...) — it just wasn't extended to cover these three. On the untyped side, the codebase has fully migrated frominterface{}toany(only 18 strayinterface{}hits, mostly in linter testdata), butanyitself is used ~14,000 times, with the highest-value targets being YAML-decoded config values (tools[toolName] any,needs: any) that are type-switched back into a small, already-known set of shapes at every call site — a pattern the codebase already solves correctly elsewhere (e.g.ActionMode stringinpkg/workflow/action_mode.go), just not consistently.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
Cluster 1: Security-scanner Finding/Issue records (semantic, high impact)
Occurrences: 9 independently-declared structs
Locations:
pkg/cli/zizmor.go:24—zizmorFinding{Ident, Desc, URL, Determinations.Severity, Locations[]}pkg/cli/poutine.go:24—poutineFinding{RuleID, Purl, Meta.Path, Meta.Line, Meta.Details}pkg/cli/grype.go:53—grypeFinding{Vulnerability.Severity, Artifact.Name/Version}pkg/cli/runner_guard.go:22—runnerGuardFinding{RuleID, Name, Severity, Description, Remediation, File, JobID, Line}pkg/cli/yamllint.go:27—yamllintIssue{File, Line, Column, Level, Message, Rule}pkg/cli/grant.go:50—grantPackageFinding{Name, Version, Decision, Licenses}pkg/cli/audit_report.go:66—Finding{Category, Severity, Title, Description, Impact}pkg/workflow/markdown_security_scanner.go:57—SecurityFinding{Category, Description, Line, Snippet}pkg/cli/validation_issue.go:4—ValidationIssue{Type, Message, Line, File}Definition Comparison (shape, not literal code — each tool names its fields differently for the same concepts):
Recommendation:
Finding{RuleID string, Severity SeverityLevel, Message string, File string, Line, Column int}type (e.g. inpkg/consoleor a newpkg/scanfindings), with aSeverityLevelenum shared across all 9 call sites.Finding, rather than declaring a bespoke struct.Cluster 2:
Footerfield copy-pasted across safe-output configs (near, high impact)Occurrences: 11
Locations:
pkg/workflow/create_issue.go:28,create_discussion.go:28,create_pull_request.go,update_issue.go:18,update_pull_request.go:21,update_discussion.go,update_release.go,submit_pr_review.go,reply_to_pr_review_comment.go,comment_memory.go,add_comment.goDefinition Comparison:
Recommendation:
pkg/workflow/safe_outputs_config_types.go:22already definesBaseSafeOutputConfig{Max, GitHubToken, GitHubApp, Staged, IssueIntent, NormalizeClosingKeywords, Samples}and is embedded by these same structs — butFooterwas never added to it.Footer *stringintoBaseSafeOutputConfigand delete the 11 duplicate declarations.Cluster 3:
TargetRepoSlug/AllowedReposcross-repo fields (near, high impact)Occurrences: 12
Locations:
create_issue.go:21-22,create_discussion.go:21-22,create_pull_request.go,create_pr_review_comment.go,create_code_scanning_alert.go,create_agent_session.go,update_project.go,comment_memory.go,add_comment.go,dispatch_workflow.go,push_to_pull_request_branch.go,safe_outputs_parser.goDefinition Comparison:
Recommendation:
CrossRepoConfig{TargetRepoSlug string, AllowedRepos []string}embeddable struct inpkg/workflow/safe_outputs_config_types.go, alongside the existingBaseSafeOutputConfig, and embed it in the 12 structs currently redeclaring both fields.Cluster 4:
CloseOlder*/CloseOlderKeydedup-key fields (near, medium impact)Occurrences: 4 (
create_issue.go,create_discussion.go,create_pull_request.go,safe_outputs_handler_registry.go)Definition Comparison:
Recommendation: Unify with a
CloseOlderConfig{Enabled *string, Key string}embeddable type, mirroring theCloseEntityConfigalias pattern already used correctly inclose_entity_helpers.go(see Cluster 9 below).Estimated effort: 1-2 hours
Cluster 5:
GitHubMCPDockerOptionsvsGitHubMCPRemoteOptions(near, medium impact)Location:
pkg/workflow/mcp_renderer_types.go:67and:1018 fields (
ReadOnly,Lockdown,LockdownFromStep,GuardPoliciesFromStep,Toolsets,Features,AllowedTools []string,GuardPolicies map[string]any) are identical between the two structs; only the transport-specific fields differ (CustomArgs/DockerImageVersionvsAuthorizationValue).Recommendation: Factor the 8 shared fields into a
GitHubMCPCommonOptionsstruct embedded by both. Estimated effort: 1 hour.Cluster 6:
workflow_errors.goerror type family (near, medium impact)WorkflowValidationError,OperationError, andConfigurationError(all inpkg/workflow/workflow_errors.go:38/98/154) shareReason,Suggestion,Timestampplus varying identity fields (Field+location+Severity/CategoryvsOperation/EntityType/EntityIDvsConfigKey).Recommendation: Share a
baseError{Reason, Suggestion string; Timestamp time.Time}embed with a customError()per type, rather than repeating the three common fields three times. Estimated effort: 1-2 hours.Cluster 7:
LockMetadatavsAgentMetadataInfo(near, medium impact)pkg/workflow/lock_schema.go:35(LockMetadata) and:52(AgentMetadataInfo) —AgentMetadataInfo's 7 fields (AgentID,AgentModel,DetectionAgentID,DetectionAgentModel,EngineBaseURLCustomized,EngineVersions,AgentImageRunner) are a verbatim subset ofLockMetadata's fields, in the same file.Recommendation: Embed
AgentMetadataInfoinsideLockMetadatainstead of maintaining both independently. Estimated effort: < 1 hour.Cluster 8: Ad-hoc status/severity/level enums, no shared vocabulary (semantic, medium impact)
ErrorSeverity int(pkg/workflow/error_recovery.go:23),AttributionStatus string(pkg/intent/resolver.go:7),CheckState string(pkg/cli/checks_command.go:23),OutcomeStatus string(pkg/cli/outcome_evaluation.go:12),PermissionLevel string(pkg/workflow/permissions.go:46),GitHubIntegrityLevel string(pkg/workflow/tools_types.go:287), plus the bareSeverity/Levelstring fields in Cluster 1.Recommendation: Not urgent to merge across domains, but a shared convention (or a common
pkg/enumspattern) would stop every package from hand-rolling its own status/severity vocabulary. Lower priority than Clusters 1-4.Cluster 9:
"Metadata"name reused for 5 unrelated concepts (semantic, low impact)Metadata(pagination,pkg/cli/mcp_registry_types.go:14),WorkflowMCPMetadata,ActionMetadata,LockMetadata,commandsHeaderMetadata— structurally unrelated, no merge needed, but the bareMetadataname inpkg/cli/mcp_registry_types.gomakes grep/navigation harder. Consider renaming toRegistryPageMetadata.Cluster 10: Six independent
*Statusstructs (semantic, low impact)WorkflowStatus,WorkflowFileStatus,LockFileStatus,DomainRunStatus,PRCommitStatus,GuardrailStatus— each represents a genuinely different concept (git state vs. lock file vs. CI check). No merge recommended; flagged only so a 7th isn't added without checking for overlap first.Cluster 11: Per-tool
*ToolConfigtypes undertools_types.go(near, low impact)9 types (
GitHubToolConfig,BoundedQueriesConfig,PlaywrightToolConfig,BashToolConfig,WebFetchToolConfig,WebSearchToolConfig,EditToolConfig,AgenticWorkflowsToolConfig,CacheMemoryToolConfig) — mostly legitimate per-tool variance, but worth a pass to confirm each needs a distinct type vs. a sharedToolConfig{Enabled bool, Args map[string]any}with tool-specific extension fields.Cluster 12: Already-correct patterns (no action needed)
CloseIssuesConfig = CloseEntityConfig,ClosePullRequestsConfig = CloseEntityConfig,CloseDiscussionsConfig = CloseEntityConfig(close_entity_helpers.go:263-265),MissingDataConfig/MissingToolConfig/ReportIncompleteConfig = IssueReportingConfig(missing_issue_reporting.go:26-42) — this is the pattern Clusters 2-4 should move toward.SpinnerWrapper(console/spinner.govsspinner_wasm.go),ProgressBar(console/progress.govsprogress_wasm.go),RepositoryFeatures(repository_features_validation.govs_wasm.go) — intentional(go/redacted):build js || wasmstubs, correctly duplicated for platform parity.Untyped Usages
Summary Statistics
anyusages (repo-wide,pkg/): ~14,149 (rough grep count including legitimate generic/JSON/reflection use — see notes below)interface{}usages: 18 (mostly in linter testdata — the codebase has already migrated toany)Category 1:
anyin function parameters for YAML-decoded config values (high impact)Example 1: Frontmatter scalar coercion, repeated per call site
pkg/workflow/engine_config_parser.go:29(parseMaxRunsValue), plusparseMaxAICreditsValue,parseMaxTurnsValue,parseHarnessMaxRetriesValue,parseMaxToolDenialsValuefunc parseMaxRunsValue(raw any) int— each function re-implements the sameint/int64/uint64/float64/stringtype-switchrawalways comes from a decoded YAML frontmattermap[string]anypkg/typeutil.ParseIntValue(raw)helper instead of re-implementing the switch per call siteExample 2: Per-tool config parsers, 10+ near-identical signatures
pkg/workflow/tools_parser.go:190-689—parseGitHubTool(val any),parseBashTool(val any), and 8+ siblingsvalis alwaystools[toolName]from a decodedmap[string]any; each function asserts tomap[string]anyorboolinternally (the implicit contract isbool | map[string]any)ToolValuewrapper with.AsMap()/.AsBool()accessors shared by allparseXToolfunctionsExample 3: GitHub Actions
needs:fieldpkg/cli/runner_guard_activation_gate.go:19—Needs any, consumed byjobNeeds(needs any) []string(line 221), type-switchingstring / []any / []stringtype JobNeeds any(documented union) with a customUnmarshalYAMLthat normalizes to[]stringonceneeds:is parsed — centralizing avoids driftCategory 2:
any/map[string]anystruct fields with a known, finite shape (high impact)Example 1:
InputDefinition.Defaultpkg/types/input_definition.go:18—Default anywith a comment "Can be string, number, or boolean"GetDefaultAsString()(same file, line 24) type-switchesstring/bool/int/int64/float64with a silent%vfallback (lines 48-49) that can produce surprising output for unexpected typesDefaultValue{Str, Num, Bool, Kind}) — the comment already documents the exact 3 legal typesExample 2:
GuardPolicies map[string]anypkg/workflow/tools_types.go:530— comment already enumerates known variants ("For GitHub: policies are represented via GitHubAllowOnlyPolicy...")map[string]json.RawMessageor a typed union keyed by server kindExample 3: Duplicated trial-result blobs
pkg/cli/trial_types.go:9-12andpkg/cli/trial_support.go:20-23—SafeOutputs map[string]any,AgenticRunInfo map[string]any,AdditionalArtifacts map[string]any, declared near-identically in two filesSafeOutputs's shape actually mirrors the already-typedworkflow.SafeOutputsConfigworkflow.SafeOutputsConfiginstead of re-decoding intomap[string]anyin two parallel structsCategory 3:
anyas return type where the concrete type is already known (medium impact)pkg/parser/schema_suggestions.go:386/395/405/417—generateStringExample(...) any,generateNumberExample(...) any,generateArrayExample(...) any,generateObjectExample(...) anyalways returnstring,float64,[]any,map[string]anyrespectively (only the top-level dispatchergenerateExampleFromSchemaat line 273 genuinely needsany, since JSON-schema type is dynamic).Category 4: Untyped string-enum constants (medium impact)
Example 1: Run-phase constants
pkg/workflow/run_phase.go:7-11—runPhaseAgent = "agent",runPhaseDetection = "detection",runPhaseEvals = "evals", compared with==across 6 engine files (claude_engine.go,codex_engine.go,gemini_engine.go,pi_engine.go,copilot_engine_execution.go) and written intoenv["GH_AW_PHASE"]type RunPhase stringwith typed constants — mirrors theActionMode stringpattern already used correctly one file over, inpkg/workflow/action_mode.go:16-24Example 2: Default-model constants mixed into an unrelated const block
pkg/constants/engine_constants.go:300-316—CopilotBYOKDefaultModel,CodexDefaultModel,AgentDefaultModel,SonnetDefaultModelsit in the same untypedconstblock as ~30*EnvVarname constantstype ModelName stringfor the*DefaultModelgroup so it isn't accidentally type-compatible with unrelated env-var-name strings in the same blockCategory 5: Untyped numeric constants (medium impact)
pkg/constants/constants.go:105-135—DefaultMCPGatewayPort = 8080,DefaultMCPServerPort = 3000,MinNetworkPort = 1,MaxNetworkPort = 65535,ClaudeLLMGatewayPort,CodexLLMGatewayPort, all plain untypedinttype Port intwith a(p Port) Valid() boolmethod using the existing Min/Max constantsRefactoring Recommendations
Priority 1: Critical — Extend
BaseSafeOutputConfig(Clusters 2-4)Steps:
Footer *stringtoBaseSafeOutputConfiginpkg/workflow/safe_outputs_config_types.go; delete the 11 duplicate declarations.CrossRepoConfig{TargetRepoSlug, AllowedRepos}and embed it in the 12 structs that redeclare both fields.CloseOlderConfig{Enabled *string, Key string}for the 4 close-older duplicates.go build ./...and the existing test suite to confirm no YAML-tag or JSON-shape regressions.Estimated effort: 4-6 hours combined
Impact: High — removes ~27 duplicate field declarations across
pkg/workflow, single source of truth for the safe-output config surfacePriority 2: High — Consolidate the security-scanner
Findingfamily (Cluster 1)Steps:
Finding{RuleID, Severity SeverityLevel, Message, File string, Line, Column int}and aSeverityLevelenum.Estimated effort: 4-6 hours
Impact: High — one severity vocabulary instead of nine, easier to onboard a new scanner integration
Priority 3: Medium — Replace YAML-decoded
anyparameters with documented union types (Category 1-2)Steps:
engine_config_parser.go) through the existingpkg/typeutil.ParseIntValuehelper instead of re-implementing the switch.ToolValuewrapper (.AsMap()/.AsBool()) for the 10+parseXTool(val any)functions intools_parser.go.ActionMode-style typed-string pattern torun_phase.go's phase constants andrunner_guard_activation_gate.go'sNeeds/JobNeeds.Estimated effort: 6-8 hours
Impact: Medium-High — centralizes several independently-drifting coercion paths
Priority 4: Low — Typed constants for ports/models, cleanup of small near-duplicates (Clusters 5-7, Category 4-5)
Steps: Add
Port,ModelName,RunPhasenamed types; mergeGitHubMCPDockerOptions/RemoteOptionsshared fields; embedAgentMetadataInfoinsideLockMetadata; share abaseErrorembed across theworkflow_errors.gofamily.Estimated effort: 4-5 hours
Impact: Low-Medium — clarity and safety, not urgent
Implementation Checklist
BaseSafeOutputConfigwithFooter,CrossRepoConfig,CloseOlderConfig(Priority 1)Findingtypes into a sharedFinding+SeverityLevel(Priority 2)ActionMode-style typed strings torun_phase.goandneeds:parsing (Priority 3)Port/ModelNametyped constants, merge small structural near-duplicates (Priority 4)go build ./...and full test suite after each consolidation stepAnalysis Metadata
_test.go)All reactions