Skip to content

Release v0.6.1#80

Merged
yimsk merged 7 commits intomainfrom
develop
Jan 3, 2026
Merged

Release v0.6.1#80
yimsk merged 7 commits intomainfrom
develop

Conversation

@yimsk
Copy link
Copy Markdown
Contributor

@yimsk yimsk commented Jan 2, 2026

Summary

Changes

  • Config file: New ~/.config/claws/config.yaml for persistent settings
  • Command completion: Tab completion now works for service aliases (e.g., sfnstepfunctions)
  • Proxy support: ALL_PROXY env var now propagates to HTTP(S)_PROXY
  • Naming alignment: Directory/package names aligned with AWS CLI conventions (kebab-case)

yimsk added 4 commits January 2, 2026 12:27
* Align registry names with AWS CLI conventions

Rename 7 services to match AWS CLI naming:
- computeoptimizer → compute-optimizer
- cognito → cognito-idp
- config → configservice
- costexplorer → ce
- eventbridge → events
- macie → macie2
- sfn → stepfunctions

Old names preserved as aliases for backward compatibility.

Closes #65

* Fix pseudo-ARN prefix to match registry service name (ce)

* Add comment explaining pseudo-ARN format for Cost Explorer

* Rename directories to align with AWS CLI conventions

- Service dirs: costexplorer→ce, eventbridge→events, sfn→stepfunctions,
  cognito→cognito-idp, config→configservice, macie→macie2,
  computeoptimizer→compute-optimizer, servicequotas→service-quotas,
  licensemanager→license-manager, networkfirewall→network-firewall
- Resource dirs: hyphenate compound names (e.g., loggroups→log-groups)
- Update imports and ARN mappings

Closes #65

* Rename bedrock dirs to kebab-case for AWS CLI consistency

* Fix DAO error strings and deduplicate client helpers

* Fix error strings and deduplicate client helpers

* Fix package names to match directory names (events, stepfunctions)

* Add develop branch to CI triggers
* feat: support ALL_PROXY environment variable (#69)

* feat: propagate ALL_PROXY to HTTP_PROXY and configure NO_PROXY

Extend proxy support based on issue #69 feedback:
- Propagate ALL_PROXY to both HTTP_PROXY and HTTPS_PROXY
- Auto-configure NO_PROXY for AWS credential endpoints:
  - 169.254.169.254 (EC2 IMDS)
  - 169.254.170.2 (ECS Task Role)
  - 169.254.170.23 (EKS Pod Identity)
- Preserve existing NO_PROXY entries, only add missing endpoints

Refs #69, #67

* fix: default NO_PROXY to IMDS only and remove proxy value from logs

- NO_PROXY now only includes EC2 IMDS (169.254.169.254) by default
- ECS/EKS endpoints can be added via config (TODO #67)
- Remove proxy URL from log output to avoid credential exposure

* refactor: simplify splitNoProxy and use tagged switch in parseFlags

* refactor: separate NO_PROXY config from ALL_PROXY propagation

configureNoProxy() now runs independently when any proxy is set,
not tied to ALL_PROXY propagation. Cleaner separation of concerns.

* refactor: remove lowercase env var support for simplicity

* refactor: remove NO_PROXY auto-configuration

* refactor: skip proxy propagation for --help/--version and cleanup test

* test: add test case for lowercase all_proxy not supported
* feat: add alias completion in command mode (#70)

Include aliases in GetSuggestions() so :cost+Tab suggests cost-explorer.
Self-referential aliases (sfn→sfn) excluded from suggestions.

* refactor: use prefix+fuzzy for diff completion

* refactor: use CutPrefix and cache GetAliases

* refactor: extract match functions to shared module

- Move fuzzyMatch and matchNamesWithFallback to match.go
- Make :tag/:tags clear behavior explicit
- Sort suggestions alphabetically (services + aliases)

* refactor: cache GetAliasesForService and sort match results

* refactor: unify command parser pattern for sort and login

* test: add cache concurrency tests and improve comments

* refactor: use CutPrefix in ecs tasks render

* refactor: use CutSuffix in parseNumericValue

* fix: quote profile name in error message for clarity

* fix: lowercase pattern in fuzzyMatch for case insensitivity

* chore: remove .claude symlink

* chore: add .claude to gitignore

* fix: lowercase pattern in matchNamesWithFallback prefix check

* fix: return defensive copy from alias cache methods
…79)

* feat: add config.yaml support for timeouts, concurrency, persistence (#67, #50)

- Add ~/.config/claws/config.yaml for configurable timeouts/concurrency
- Add --persist/--no-persist flags for region/profile persistence
- Persist disabled by default, startup values always loaded
- Replace hardcoded timeout constants with config values

* docs: add config file docs, use config for max concurrent fetches

* refactor: remove unused ReloadFile, add negative value validation

* refactor: unify mutex pattern in config package

* refactor: add thread safety to Save, unify GetStartup lock pattern

* refactor: add shared lock helpers, atomic config save

* refactor: rename NoPersist to PersistEnabled for clarity

* refactor: add MetricsWindow config, consolidate lock helpers

- Add MetricsWindow (15m default) to FileConfig for CloudWatch metrics
- Move lock helpers from internal/sync to internal/config
- Update cloudwatch.go to use config.File().MetricsWindow()

* refactor: move MetricsWindow to cloudwatch section, unify PersistenceEnabled naming

* refactor: unify PersistenceEnabled to FileConfig, copy slice in GetStartup

* fix: deep copy Regions in Save(), refactor startup config logic

- Fix data race in Save() by deep copying Startup.Regions slice
- Extract applyStartupConfig() helper for cleaner precedence handling
- Reduce GetStartup() calls from 2 to 1

Addresses PR #79 review feedback.

* fix: apply startup regions regardless of profile source
yimsk added 3 commits January 3, 2026 01:01
* Align naming conventions with AWS CLI (#71)

* Align registry names with AWS CLI conventions

Rename 7 services to match AWS CLI naming:
- computeoptimizer → compute-optimizer
- cognito → cognito-idp
- config → configservice
- costexplorer → ce
- eventbridge → events
- macie → macie2
- sfn → stepfunctions

Old names preserved as aliases for backward compatibility.

Closes #65

* Fix pseudo-ARN prefix to match registry service name (ce)

* Add comment explaining pseudo-ARN format for Cost Explorer

* Rename directories to align with AWS CLI conventions

- Service dirs: costexplorer→ce, eventbridge→events, sfn→stepfunctions,
  cognito→cognito-idp, config→configservice, macie→macie2,
  computeoptimizer→compute-optimizer, servicequotas→service-quotas,
  licensemanager→license-manager, networkfirewall→network-firewall
- Resource dirs: hyphenate compound names (e.g., loggroups→log-groups)
- Update imports and ARN mappings

Closes #65

* Rename bedrock dirs to kebab-case for AWS CLI consistency

* Fix DAO error strings and deduplicate client helpers

* Fix error strings and deduplicate client helpers

* Fix package names to match directory names (events, stepfunctions)

* Add develop branch to CI triggers

* feat: propagate ALL_PROXY to HTTP_PROXY/HTTPS_PROXY (#74)

* feat: support ALL_PROXY environment variable (#69)

* feat: propagate ALL_PROXY to HTTP_PROXY and configure NO_PROXY

Extend proxy support based on issue #69 feedback:
- Propagate ALL_PROXY to both HTTP_PROXY and HTTPS_PROXY
- Auto-configure NO_PROXY for AWS credential endpoints:
  - 169.254.169.254 (EC2 IMDS)
  - 169.254.170.2 (ECS Task Role)
  - 169.254.170.23 (EKS Pod Identity)
- Preserve existing NO_PROXY entries, only add missing endpoints

Refs #69, #67

* fix: default NO_PROXY to IMDS only and remove proxy value from logs

- NO_PROXY now only includes EC2 IMDS (169.254.169.254) by default
- ECS/EKS endpoints can be added via config (TODO #67)
- Remove proxy URL from log output to avoid credential exposure

* refactor: simplify splitNoProxy and use tagged switch in parseFlags

* refactor: separate NO_PROXY config from ALL_PROXY propagation

configureNoProxy() now runs independently when any proxy is set,
not tied to ALL_PROXY propagation. Cleaner separation of concerns.

* refactor: remove lowercase env var support for simplicity

* refactor: remove NO_PROXY auto-configuration

* refactor: skip proxy propagation for --help/--version and cleanup test

* test: add test case for lowercase all_proxy not supported

* feat: add alias completion in command mode (#76)

* feat: add alias completion in command mode (#70)

Include aliases in GetSuggestions() so :cost+Tab suggests cost-explorer.
Self-referential aliases (sfn→sfn) excluded from suggestions.

* refactor: use prefix+fuzzy for diff completion

* refactor: use CutPrefix and cache GetAliases

* refactor: extract match functions to shared module

- Move fuzzyMatch and matchNamesWithFallback to match.go
- Make :tag/:tags clear behavior explicit
- Sort suggestions alphabetically (services + aliases)

* refactor: cache GetAliasesForService and sort match results

* refactor: unify command parser pattern for sort and login

* test: add cache concurrency tests and improve comments

* refactor: use CutPrefix in ecs tasks render

* refactor: use CutSuffix in parseNumericValue

* fix: quote profile name in error message for clarity

* fix: lowercase pattern in fuzzyMatch for case insensitivity

* chore: remove .claude symlink

* chore: add .claude to gitignore

* fix: lowercase pattern in matchNamesWithFallback prefix check

* fix: return defensive copy from alias cache methods
Sync main into develop (merge commit)
@claude
Copy link
Copy Markdown

claude Bot commented Jan 2, 2026

PR Review: Release v0.6.1

This is a well-structured release that adds several valuable features. Below is a comprehensive review covering code quality, potential issues, performance, security, and test coverage.


✅ Strengths

1. Excellent Code Quality

  • Clean architecture: The new internal/config/file.go is well-organized with clear separation of concerns
  • Comprehensive test coverage: 284 lines of tests in file_test.go covering edge cases (empty values, invalid input, partial configs, concurrency)
  • Good error handling: Proper use of fmt.Errorf with %w for error wrapping
  • Thread-safe implementation: Proper use of sync.RWMutex with helper functions withRLock and doWithLock
  • Atomic file writes: The Save() function uses temp file + rename pattern for atomic writes (lines 189-209)
  • Documentation: Clear comments and well-structured README updates

2. Good Design Patterns

  • Singleton pattern for config using sync.Once (lines 126-134)
  • Builder pattern for defaults via DefaultFileConfig()
  • Custom YAML marshaling for Duration type with proper validation
  • Graceful degradation: Falls back to defaults when config file doesn't exist

3. Proper Precedence Handling

The applyStartupConfig function correctly implements configuration precedence:

  1. CLI flags (highest priority)
  2. Config file startup section
  3. AWS SDK defaults (lowest priority)

⚠️ Issues & Suggestions

1. Race Condition in Persistence Logic (internal/app/app.go:336-345, 366-375)

Issue: When regions/profiles change, the code reads from config.Global() and writes to config.File() without synchronization. If multiple region/profile changes happen rapidly, there's a potential race condition.

Location: internal/app/app.go:336-345 and 366-375

case navmsg.RegionChangedMsg:
    if config.File().PersistenceEnabled() {
        profile := ""
        if sel := config.Global().Selection(); sel.IsNamedProfile() {  // Read
            profile = sel.ProfileName
        }
        config.File().SetStartup(msg.Regions, profile)  // Write
        if err := config.File().Save(); err != nil {     // Save

Recommendation: Consider debouncing the Save() calls or using a write-behind cache pattern to avoid excessive disk I/O on rapid changes.

2. Missing Validation for Config File Values

Issue: While the code validates CLI flags for profile names and regions (lines 42-51 in main.go), it doesn't validate values loaded from the config file.

Location: internal/config/file.go:137-158

Risk: A manually edited config file with invalid values (e.g., negative timeouts, invalid region format) could cause unexpected behavior.

Recommendation: Add validation in the Load() function:

// After yaml.Unmarshal
if err := cfg.validate(); err != nil {
    return nil, fmt.Errorf("invalid config: %w", err)
}

3. Potential File Descriptor Leak (Minor)

Location: internal/config/file.go:196-203

Issue: If tmpFile.Write() fails, the file is closed and removed. However, if tmpFile.Close() fails after a successful write, the temp file is removed but the close error is propagated, which is correct. This is actually well-handled, but consider adding a comment to clarify the error handling strategy.

Recommendation: Add a comment explaining the cleanup strategy.

4. Double Default Check Pattern

Location: Multiple getter methods like AWSInitTimeout() (lines 235-241)

func (c *FileConfig) AWSInitTimeout() time.Duration {
    return withRLock(&c.mu, func() time.Duration {
        if c.Timeouts.AWSInit == 0 {  // Check again despite applyDefaults
            return DefaultAWSInitTimeout
        }
        return c.Timeouts.AWSInit.Duration()
    })
}

Observation: This double-checks defaults even though applyDefaults() is called after loading. While this provides defense-in-depth, it's redundant if applyDefaults() always runs.

Recommendation: Either remove the zero-checks in getters (rely on applyDefaults()) or document why defense-in-depth is necessary here.

5. No Upper Bounds Validation

Issue: Users could set extreme values in the config file:

timeouts:
  aws_init: 24h  # Probably not intended
concurrency:
  max_fetches: 100000  # Could exhaust resources

Recommendation: Add sensible upper bounds validation:

  • Timeouts: max 5-10 minutes
  • Concurrency: max 500-1000 fetches

🔒 Security Considerations

1. File Permissions

  • Config directory created with 0755 permissions (readable by others)
  • Config file permissions inherit from os.CreateTemp (typically 0600)

Note: While 0755 for the directory is standard, ensure sensitive data isn't stored in the config. Currently, the config only contains timeouts and settings, not credentials, which is good.

2. Path Traversal

  • Uses filepath.Join() correctly
  • No user-supplied paths are used in file operations
  • Home directory is validated

3. Denial of Service ⚠️

  • No limits on startup regions array size
  • A malicious config could specify thousands of regions, causing excessive API calls

Recommendation: Limit the maximum number of startup regions (e.g., 20).


🚀 Performance Considerations

1. Good Practices

  • Singleton pattern avoids repeated file reads
  • Atomic writes prevent partial reads
  • Lock granularity is appropriate (struct-level, not global)

2. Potential Improvements

Frequent Save() Calls: Every region/profile change triggers a file write. For users rapidly switching contexts, this could cause unnecessary I/O.

Recommendation:

  • Implement debouncing (e.g., save at most once per second)
  • Or use a "dirty flag" and save on graceful shutdown

Lock Contention: The generic withRLock pattern allocates a closure on every call. For hot paths, this could add GC pressure.

Recommendation: For frequently-called getters, consider caching timeout values after load rather than locking on every access.


✅ Test Coverage

Excellent Coverage

The test suite covers:

  • ✅ Duration marshaling/unmarshaling (including edge cases)
  • ✅ Default configuration
  • ✅ Missing config file handling
  • ✅ Round-trip save/load
  • ✅ Partial config files
  • ✅ Invalid values
  • ✅ Empty values
  • ✅ Negative values

Missing Test Cases

  1. Concurrent access: No tests for concurrent Save() and Load() operations
  2. SetPersistenceEnabled(): Not tested
  3. File system errors: No tests for permission errors, disk full, etc.
  4. Large values: No tests for extreme timeout values or large region arrays

Recommendation: Add tests for:

func TestFileConfig_ConcurrentAccess(t *testing.T)
func TestFileConfig_PersistenceOverride(t *testing.T)  
func TestSave_PermissionDenied(t *testing.T)

📋 Minor Issues

1. Inconsistent Naming

  • ConfigDir() and ConfigPath() are package-level functions
  • Most other functions are methods on FileConfig

Suggestion: Consider making them methods or document why they're package-level.

2. Lock Helper Location (internal/config/lock.go)

The withRLock and doWithLock helpers are in a separate file but only used by file.go. They were likely moved from config.go to avoid duplication.

Observation: This is fine, but the helpers are very generic. Consider if they provide value over inline lock/unlock.

3. YAML Omitempty Consistency

Most fields use omitempty, but PersistenceConfig.Enabled doesn't:

type PersistenceConfig struct {
    Enabled bool `yaml:"enabled"`  // No omitempty
}

Impact: The config file will always show persistence: {enabled: false} even when not set.

Recommendation: Either add omitempty or document that this is intentional to make the setting explicit.


🎯 Summary

Category Rating Notes
Code Quality ⭐⭐⭐⭐⭐ Excellent structure, clean code, good practices
Test Coverage ⭐⭐⭐⭐☆ Very good, but missing concurrency tests
Security ⭐⭐⭐⭐☆ Good, minor DoS risk with unbounded arrays
Performance ⭐⭐⭐⭐☆ Good design, could optimize frequent saves
Documentation ⭐⭐⭐⭐⭐ Excellent README updates with examples

💡 Recommendations Summary

High Priority

  1. Add upper bounds validation for concurrency and timeout values
  2. Add validation for config file values (regions, profiles)
  3. Consider debouncing Save() calls to reduce I/O

Medium Priority

  1. Add concurrency tests for config file operations
  2. Document the double-default-check pattern or simplify it
  3. Limit maximum number of startup regions

Low Priority

  1. Add comments explaining file write error handling strategy
  2. Consider caching timeout values to reduce lock contention
  3. Make YAML omitempty usage consistent

✨ Conclusion

This is a high-quality PR that adds valuable functionality with excellent test coverage and clean architecture. The issues identified are mostly minor refinements rather than critical bugs. The atomic file writes, thread-safe access, and graceful fallbacks demonstrate mature engineering practices.

Recommendation: Approve with minor suggestions for follow-up improvements.

Great work! 🚀


Generated by Claude Code PR Review

@yimsk yimsk merged commit 5726c16 into main Jan 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant