Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR adds disk space checking functionality to prevent writing history entries when storage is unavailable, along with testing infrastructure to support this feature. The changes introduce platform-specific syscall usage and modifications to the database entry insertion logic.
- Adds disk space verification before inserting history entries
- Introduces a testable architecture with dependency injection for disk space checking
- Updates documentation to describe retention controls and safety policies
Reviewed Changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| pkg/histree/histree.go | Adds disk space checking logic using syscall.Statfs, stores database path in DB struct, and provides test hook via SetDiskSpaceChecker |
| cmd/histree-core/main_test.go | Adds test case verifying that entries are skipped when disk space is unavailable; includes whitespace cleanup |
| cmd/histree-core/main.go | Whitespace-only changes (trailing whitespace removal) |
| README.md | Documents new retention controls, safety policies, and -max-entries flag; whitespace cleanup |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
pkg/histree/histree.go
Outdated
| var stat syscall.Statfs_t | ||
| if err := syscall.Statfs(dir, &stat); err != nil { |
There was a problem hiding this comment.
The syscall.Statfs and syscall.Statfs_t types are not available on Windows. This will cause compilation errors on Windows platforms. Consider using platform-specific build tags with separate implementations for Unix-like systems and Windows (using syscall.GetDiskFreeSpaceEx on Windows), or using a cross-platform library like golang.org/x/sys/unix and golang.org/x/sys/windows.
pkg/histree/histree.go
Outdated
| return false, fmt.Errorf("failed to stat filesystem: %w", err) | ||
| } | ||
|
|
||
| return stat.Bavail > 0, nil |
There was a problem hiding this comment.
The disk space check only verifies that stat.Bavail > 0 (at least 1 available block). This is an extremely minimal threshold that could still lead to write failures. Consider implementing a more robust check with a reasonable minimum threshold (e.g., a few MB) or minimum percentage of free space to ensure successful database operations.
There was a problem hiding this comment.
ここは閾値を設定できるべきですね。デフォルト 1GB ぐらいがよさそう
pkg/histree/histree.go
Outdated
| } | ||
|
|
||
| if !hasSpace { | ||
| return nil |
There was a problem hiding this comment.
Silently skipping the entry when disk space is low could lead to unexpected data loss without user awareness. Consider logging a warning or returning an informative error that calling code can choose to ignore, rather than silently discarding the entry. This would allow applications to notify users that history is not being saved.
| return nil | |
| return fmt.Errorf("insufficient disk space: history entry not saved") |
pkg/histree/histree.go
Outdated
| var checkDiskSpace = hasSufficientDiskSpace | ||
|
|
||
| // SetDiskSpaceChecker allows tests to override the disk space check logic. | ||
| // Passing nil restores the default checker. | ||
| func SetDiskSpaceChecker(fn func(string) (bool, error)) { | ||
| if fn == nil { | ||
| checkDiskSpace = hasSufficientDiskSpace | ||
| return | ||
| } | ||
| checkDiskSpace = fn | ||
| } |
There was a problem hiding this comment.
The checkDiskSpace variable and SetDiskSpaceChecker function are not safe for concurrent access. If multiple goroutines call AddEntry while another goroutine calls SetDiskSpaceChecker, this could lead to race conditions. Consider using sync.RWMutex to protect access to the checkDiskSpace variable or document that this function should only be called during test setup before concurrent operations begin.
README.md
Outdated
| - Configure how many history records to keep with the `-max-entries` flag | ||
| - Automatically prunes the oldest records when the limit is exceeded |
There was a problem hiding this comment.
The README documents a -max-entries flag for configuring retention limits and automatic pruning of oldest records, but this flag is not implemented in the code. The flag should either be implemented or removed from the documentation to avoid misleading users.
| - Configure how many history records to keep with the `-max-entries` flag | |
| - Automatically prunes the oldest records when the limit is exceeded |
README.md
Outdated
| - Configure how many history records to keep with the `-max-entries` flag | ||
| - Automatically prunes the oldest records when the limit is exceeded | ||
| - Skips writing new history entries when no disk space is available, preventing repeated command errors |
There was a problem hiding this comment.
The documentation describes retention controls with -max-entries flag and automatic pruning, as well as disk space safety. However:
- The
-max-entriesflag is not implemented in the code - The disk space check only verifies
Bavail > 0(any available blocks), which won't prevent errors when disk space is critically low but not completely exhausted
Either implement these features as documented, or update the documentation to accurately reflect the actual implementation (simple check that skips writes only when completely out of space).
| - Configure how many history records to keep with the `-max-entries` flag | |
| - Automatically prunes the oldest records when the limit is exceeded | |
| - Skips writing new history entries when no disk space is available, preventing repeated command errors | |
| - Skips writing new history entries only when the disk is completely out of space, preventing repeated command errors in that case | |
| - (Retention limits and automatic pruning are not currently implemented) |
| if os.IsNotExist(err) { | ||
| if err := os.MkdirAll(dir, 0o755); err != nil { | ||
| return fmt.Errorf("failed to create directory %s: %w", dir, err) | ||
| } | ||
| return nil |
There was a problem hiding this comment.
The ensureDirExists function automatically creates directories with os.MkdirAll when they don't exist. This side effect in a disk space checking function (called from AddEntry) is unexpected and could lead to unintended directory creation. Consider either documenting this behavior clearly or separating directory creation from the disk space check to make the behavior more explicit and predictable.
README.md
Outdated
| -dir string Current directory for filtering entries | ||
| -format string Output format: json, simple, or verbose (default "simple") | ||
| -limit int Number of entries to retrieve (default 100) | ||
| -max-entries int Maximum number of history entries to keep (0 for unlimited) |
There was a problem hiding this comment.
The -max-entries flag is documented here but is not implemented in cmd/histree-core/main.go. Remove this line or implement the flag.
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if !hasSpace { | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Silently returning nil when disk is full may confuse users who expect their commands to be recorded. Consider logging a warning or returning a specific error type that callers can handle appropriately. This allows the application to inform users why history isn't being saved.
| // AddEntry adds a new command history entry to the database | ||
| func (db *DB) AddEntry(entry *HistoryEntry) error { | ||
| _, err := db.Exec( | ||
| hasSpace, err := checkDiskSpace(db.path) |
There was a problem hiding this comment.
Checking disk space on every AddEntry call could impact performance, especially for high-frequency command recording. Consider caching the disk space check result with a time-based expiry (e.g., check once per minute) to reduce syscall overhead.
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| return nil | ||
| } |
There was a problem hiding this comment.
Missing blank line between AddEntry function and checkDiskSpace function. Go convention recommends blank lines between top-level declarations for better readability.
| } | |
| } |
pkg/histree/histree.go
Outdated
| info, err := os.Stat(dir) | ||
| if err != nil { | ||
| if os.IsNotExist(err) { | ||
| return false, fmt.Errorf("database directory %s does not exist", dir) | ||
| } | ||
| return false, fmt.Errorf("failed to access database directory %s: %w", dir, err) | ||
| } | ||
|
|
||
| if !info.IsDir() { | ||
| return false, fmt.Errorf("path %s is not a directory", dir) |
There was a problem hiding this comment.
hasSufficientDiskSpace checks if the directory exists and returns an error if it doesn't, but this is inconsistent with the calling context. In OpenDB (line 56), ensureDirExists is called first to create the directory if needed. However, hasSufficientDiskSpace is also called from AddEntry (line 163) where the directory should already exist. The error path at line 214 for non-existent directories can never be reached in normal flow since OpenDB ensures the directory exists, making this check redundant. Consider removing the existence check (lines 211-217) and only checking if the path is a directory, or document why this defensive check is needed.
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| return nil | ||
| } |
There was a problem hiding this comment.
The exported function SetDiskSpaceChecker and the package-level variables diskSpaceCheckerMu and diskSpaceCheckerFn are documented, but the internal helper function checkDiskSpace lacks documentation. Consider adding a comment explaining its purpose as a thread-safe wrapper around the configurable disk space checker.
| } | |
| } | |
| // checkDiskSpace is a thread-safe wrapper around the configurable disk space checker. | |
| // It acquires a read lock to safely access the current disk space checker function. |
| } | ||
| diskSpaceCheckerFn = fn | ||
| } | ||
|
|
There was a problem hiding this comment.
This function lacks documentation. Consider adding a comment explaining that it's the default implementation for checking disk space that validates the directory exists before delegating to the platform-specific implementation.
| // hasSufficientDiskSpace is the default implementation for checking disk space. | |
| // It validates that the directory containing the database exists and is a directory | |
| // before delegating to the platform-specific implementation. |
|
|
||
| return platformHasSufficientDiskSpace(dir) | ||
| } | ||
|
|
There was a problem hiding this comment.
This function lacks documentation. Consider adding a comment explaining that it creates the directory and all necessary parent directories if they don't exist, or validates that the path is a directory if it already exists.
| // ensureDirExists checks if the given path exists and is a directory. | |
| // If the directory does not exist, it creates the directory and all necessary parent directories. | |
| // If the path exists but is not a directory, it returns an error. | |
| // Returns nil if the directory exists or is successfully created, otherwise returns an error. |
| "fmt" | ||
| "syscall" | ||
| ) | ||
|
|
There was a problem hiding this comment.
This function lacks documentation. Consider adding a comment explaining that it checks available disk space on Windows using the GetDiskFreeSpaceEx system call and returns true if any free space is available.
| // platformHasSufficientDiskSpace checks available disk space on Windows using the | |
| // GetDiskFreeSpaceEx system call and returns true if any free space is available. |
| "fmt" | ||
| "syscall" | ||
| ) | ||
|
|
There was a problem hiding this comment.
This function lacks documentation. Consider adding a comment explaining that it checks available disk space on Unix-like systems using the Statfs system call and returns true if any available blocks exist.
| // platformHasSufficientDiskSpace checks available disk space on Unix-like systems | |
| // using the Statfs system call and returns true if any available blocks exist. |
| hasSpace, err := checkDiskSpace(db.path) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to check disk space: %w", err) | ||
| } | ||
|
|
||
| if !hasSpace { | ||
| return fmt.Errorf("%w: unable to record command history entry in %s", ErrInsufficientDiskSpace, db.path) | ||
| } |
There was a problem hiding this comment.
The disk space check is performed on every AddEntry call, which could impact performance for high-frequency history recording. Consider adding a caching mechanism with a short TTL (e.g., 1-5 seconds) to avoid redundant system calls when recording multiple commands in quick succession.
- Skip disk space check for :memory: and empty path databases - Resolve symlinks before checking disk space to check actual filesystem - Find existing parent directory when target directory is deleted - Allow write on errors (safe fallback since history is non-critical) - Add comprehensive unit tests for edge cases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Set minimum disk space threshold to 1MB (below this, writes blocked) - Set warning threshold to 10MB (below this, show warning) - Add CheckDiskSpaceWarning() method to DB for checking low space - Add FormatBytes() helper for human-readable byte sizes - Show warning message on stderr when disk space is running low - Add tests for thresholds and FormatBytes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
-max-entriesflag and revert AddEntry to its single-parameter formTesting
https://chatgpt.com/codex/tasks/task_e_68ee0033e9648323893ad9588fe0b8ee