Conversation
- Interactive prompt asks for GitHub repository (owner/repo format) with validation - Interactive prompt asks for authentication method preference (env/gh/token) - Configuration saved to ~/.config/ghissues/config.toml with 0600 permissions - Setup skipped if config file already exists - Setup can be re-run with `ghissues config` command Co-Authored-By: Claude <noreply@anthropic.com>
- Added internal/auth package with GetToken() function for authentication chain - Added internal/github package with token validation via GitHub API - Updated setup to prompt for token when using token auth method - Updated main to validate token on startup with helpful error messages Co-Authored-By: Claude <noreply@anthropic.com>
- Add database.path config field to Config struct - Create internal/db package with GetPath, EnsureDir, IsWritable functions - Add --db flag to CLI for overriding database path - Flag takes precedence over config file - Parent directories created automatically - Clear error if path is not writable Closes #4
- Add Issue, Comment, Label structs with JSON parsing - Implement FetchIssues() with automatic pagination (Link header) - Implement FetchComments() with automatic pagination - Add SQLite database operations (Open, UpsertIssue, UpsertComment, etc.) - Create database schema with foreign key constraints and cascade delete - Add `ghissues sync` subcommand with progress bar - Handle Ctrl+C gracefully with context cancellation Closes #3
- Added Display.Columns config for configurable issue list columns - Added ListIssues and GetIssue functions to db/ops.go - Created TUI with issue list view using tview library - Vertical split layout: issue list (left) + details (right) - Vim keys (j/k) and arrow keys for navigation - Issue count shown in status bar - Configurable columns: number, title, author, date, comments Files: - internal/config/config.go: Added Display struct with Columns field - internal/db/ops.go: Added IssueList struct, ListIssues, GetIssue functions - cmd/ghissues/tui.go: New TUI with issue list, navigation, status bar - cmd/ghissues/main.go: Added tui subcommand handler - go.mod: Added tview and tcell dependencies Tests: - config_display_test.go: 3 tests for Display.Columns - ops_list_test.go: 6 tests for ListIssues/GetIssue - tui_test.go: 10 tests for TUI helper functions Co-Authored-By: Claude <noreply@anthropic.com>
- Added SortOption and SortOrder types to config Display struct - Available sort options: updated (default), created, number, comments - Sort order: desc (default) and asc - TUI keybindings: 's' to cycle sort options, 'S' to reverse order - Current sort shown in status bar - Sort preference persisted to config file Co-Authored-By: Claude <noreply@anthropic.com>
- Added IssueDetail struct with Body, Labels, Assignees fields - Created GetIssueDetail, GetLabels, GetAssignees, GetComments functions - Added glamour markdown rendering with 'm' key toggle - Added scrollable detail view with full issue information - Added comments modal on Enter key - Added 8 new tests for database functions Co-Authored-By: Claude <noreply@anthropic.com>
- Drill-down view replaces main interface when activated (full-page view) - Shows issue title/number as header in comments view - Comments displayed chronologically (already done) - Each comment shows: author, date, body (markdown rendered with glamour) - Toggle markdown rendering with m key in comments view - Scrollable comment list (using SetScrollable(true)) - Esc or q returns to issue list view - Updated help modal with comments view keybindings - Dynamic status bar showing current view context Files changed: - cmd/ghissues/tui.go (221 lines): Updated comments view to drill-down, added markdown toggle, Esc/q handling, dynamic status bar - cmd/ghissues/tui_test.go (53 lines): Added tests for formatComments with markdown rendering - .ralph-tui/progress.md: Added new patterns and learnings Co-Authored-By: Claude <noreply@anthropic.com>
- Auto-refresh triggered on app launch via RunTUIWithRefresh - Manual refresh with 'r' or 'R' keybinding - Progress bar shown in status bar during refresh - Incremental sync using GitHub API's `since` parameter - Handles deleted issues (removes from local db) - Handles new comments on existing issues New patterns documented: - Incremental Sync Pattern - TUI Refresh Pattern - ProgressCallback Pattern Co-Authored-By: Claude <noreply@anthropic.com>
- Created internal/errors package with error classification functions - Minor errors (network timeout, rate limit) shown in status bar - Critical errors (invalid token, database corruption) shown in modal - Modal errors require acknowledgment before continuing - Errors include actionable guidance with retry capability Co-Authored-By: Claude <noreply@anthropic.com>
- Added FormatRelativeTime function for relative time formatting - Added GetLastSyncedDisplay helper for status bar display - Updated status bar to show "Last synced: <relative time>" - Added tests for both new functions Co-Authored-By: Claude <noreply@anthropic.com>
- Added persistent footer for context-sensitive keybindings - Footer shows: help | comments | refresh (issue list view) - Footer shows: back | markdown/raw (comments view) - Help modal dismissible with ? or Esc keys - Added GetFooterDisplay function for testable footer text - Added TestGetFooterDisplay with 3 test cases Acceptance Criteria Met: - ? opens help overlay with all keybindings organized by context - Persistent footer shows context-sensitive common keys - Footer updates based on current view (list, comments) - Help overlay dismissible with ? or Esc Co-Authored-By: Claude <noreply@anthropic.com>
- Added 6 built-in themes: default, dracula, gruvbox, nord, solarized-dark, solarized-light - Theme selected via config file display.theme - Theme can be previewed/changed with `ghissues themes` command - Themes use tcell for consistent styling Files: - internal/themes/themes.go (new file with theme definitions) - internal/themes/themes_test.go (new file with 8 tests) - internal/config/config.go (added Theme field) - internal/config/config_display_test.go (added theme tests) - cmd/ghissues/themes.go (new file with themes subcommand) - cmd/ghissues/main.go (added themes handler) - cmd/ghissues/tui.go (apply theme colors) - cmd/ghissues/tui_test.go (added TestGetThemeColor) Co-Authored-By: Claude <noreply@anthropic.com>
- Config file supports multiple repository entries via Repositories slice - Each repository tracked with DefaultRepository for selection - ghissues --repo owner/repo selects which repo to view - ghissues repos lists configured repositories - Helper functions for add/remove/set-default operations - Backward compatible with legacy single Repository field Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a full GitHub Issues TUI implementation, including configuration, auth, GitHub API client, SQLite persistence, theming, and extensive tests, plus wiring for a ghissues CLI with sync and TUI commands.
Changes:
- Add configuration, auth, GitHub client, SQLite DB schema/ops, error-classification, and theming packages, all with unit tests.
- Implement a TUI (
cmd/ghissues/tui.go) that lists issues, shows details, and has a comments drill‑down and sort/markdown toggling. - Update
tasks/prd.jsonto mark multiple user stories as passing and add completion metadata, and introduce Go module dependencies ingo.mod/go.sum.
Reviewed changes
Copilot reviewed 33 out of 51 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
tasks/prd.json |
Marks many stories as passes: true, expands labels arrays, adds completionNotes and new metadata.updatedAt field. |
internal/themes/themes.go / themes_test.go |
Defines theme model and six concrete themes (default, dracula, gruvbox, nord, solarized-{dark,light}) plus tests for lookup, validation, formatting, and color correctness. |
internal/github/client.go / client_test.go / issue_test.go / fetch_test.go |
Implements a GitHub API client (token validation, fetching issues/comments with pagination and error handling) and struct tests and basic request-shape tests. |
internal/errors/errors.go / errors_test.go |
Adds error categorization (minor vs critical), user-facing hints and UIError wrapper, with comprehensive tests of classification and hints. |
internal/db/db.go / db_test.go |
Adds DB path resolution, writability checks, and path error type with tests. |
internal/db/ops.go / ops_test.go / ops_list_test.go |
Defines SQLite schema and CRUD/lookup helpers for issues, labels, assignees, comments, plus many tests for persistence, listing/sorting, and relations. |
internal/config/config.go / config_test.go / config_display_test.go |
Implements TOML-based config (multi-repo support, display prefs, sort, theme) with read/write and a large test suite. |
internal/auth/auth.go / auth_test.go |
Implements token discovery (env, config, gh CLI) and tests around precedence and edge-cases. |
cmd/ghissues/tui.go / tui_test.go |
Implements the TUI (issue list, detail view, comments drill‑down, sort/markdown toggles, footer/status text) and tests for formatting helpers and display logic. |
cmd/ghissues/themes.go |
CLI helper to list/select themes and persist selection to config. |
cmd/ghissues/repos.go |
CLI helpers to list/add/remove/set-default repositories in config. |
cmd/ghissues/sync.go (context) |
Provides RefreshSync used by the TUI for background sync and a runSync command; read only for context. |
go.mod / go.sum |
Introduces Go module metadata, dependencies for TUI/theming/SQLite/TOML, and Go version directive. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Review from Claude Code using Sonnet 4.5: Security Review - GitHub Issues TUIReview Date: 2026-01-21 Executive SummaryThis security review examined the GitHub Issues TUI codebase for security vulnerabilities, exposed secrets, and security best practices. The codebase demonstrates strong fundamentals in SQL injection prevention, secure API communication, and authentication handling. However, two critical issues were identified that require immediate attention: a database file committed to git and a missing .gitignore file. Overall Risk Level: MEDIUM Table of Contents
Critical Issues 🔴Issue #1: Database File Committed to GitSeverity: CRITICAL Description:
Evidence: $ git ls-files | grep .ghissues.db
.ghissues.db
$ ls -lh .ghissues.db
-rw-r--r--@ 1 shepbook staff 648K Jan 21 21:07 .ghissues.dbImpact:
Recommendation:
Issue #2: Missing .gitignore FileSeverity: CRITICAL Description:
Evidence: $ cat .gitignore
cat: .gitignore: No such file or directoryImpact:
Recommendation: # Database files
.ghissues.db
*.db
*.sqlite
*.sqlite3
# Configuration files that may contain secrets
config.toml
.config/
# Environment files
.env
.env.*
!.env.example
# Build artifacts
ghissues
*.exe
*.dll
*.so
*.dylib
# Test coverage
*.out
coverage.txt
*.test
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# Ralph TUI artifacts (optional - contains config)
.ralph-tui/High Priority Issues 🟡Issue #3: Plaintext Token StorageSeverity: HIGH Description: Code Reference: // config.go:245
return os.WriteFile(path, data, 0600)Current Protection:
Limitations:
Impact:
Recommendation:
Example implementation approach: import "github.com/zalando/go-keyring"
// Store token
err := keyring.Set("ghissues", "github-token", token)
// Retrieve token
token, err := keyring.Get("ghissues", "github-token")Issue #4: No Input Masking for Token EntrySeverity: MEDIUM-HIGH Description: Code Reference: func promptToken(scanner *bufio.Scanner) (string, error) {
fmt.Print("Enter your GitHub Personal Access Token: ")
if !scanner.Scan() {
// ... error handling
}
token := strings.TrimSpace(scanner.Text())
// Token is visible during typing
}Impact:
Recommendation: import (
"golang.org/x/term"
"os"
"syscall"
)
func promptToken() (string, error) {
fmt.Print("Enter your GitHub Personal Access Token: ")
// Read password without echo
byteToken, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", fmt.Errorf("failed to read token: %w", err)
}
fmt.Println() // Add newline after input
token := strings.TrimSpace(string(byteToken))
return token, nil
}Dependencies to add: go get golang.org/x/termIssue #5: Configuration File Tracked in GitSeverity: MEDIUM Description: Current Contents: configVersion = "2.1"
tracker = "json"
agent = "claude"
maxIterations = 10
autoCommit = true
prompt_template = "ralph-prompt.hbs"Risk Assessment:
Recommendation:
Security Strengths ✅Authentication & AuthorizationMulti-Source Token ResolutionFiles: The application implements a robust three-tier authentication system with proper precedence:
Code Example: func GetToken() (string, TokenSource, error) {
// 1. Try GITHUB_TOKEN environment variable
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
return token, TokenSourceEnv, nil
}
// 2. Try config file
if config.Exists() {
cfg, err := config.Load()
if err == nil && cfg.Token != "" {
return cfg.Token, TokenSourceConfig, nil
}
}
// 3. Try GitHub CLI
token, err := getGhAuthToken()
if err == nil && token != "" {
return token, TokenSourceGhCli, nil
}
return "", "", ErrNoAuthFound
}Benefits:
No Hardcoded CredentialsScope: Entire codebase Comprehensive search conducted for common token patterns:
Result: Zero hardcoded credentials detected HTTPS-Only CommunicationFiles: All GitHub API communication uses HTTPS exclusively: req, err := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/user", nil)Verified Endpoints:
No insecure HTTP fallback found Token ValidationFile: Proper token validation with comprehensive error handling: func (c *Client) ValidateToken(ctx context.Context) error {
resp, err := http.DefaultClient.Do(req)
if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf(`invalid GitHub token.
Please check your token and update it:
1. For environment variable: export GITHUB_TOKEN=your_token
2. For config file: run 'ghissues config' to update
3. For GitHub CLI: run 'gh auth refresh'`)
}
if resp.StatusCode == http.StatusForbidden {
remaining := resp.Header.Get("X-RateLimit-Remaining")
if remaining == "0" {
resetTime := resp.Header.Get("X-RateLimit-Reset")
return fmt.Errorf(`GitHub API rate limit exceeded.
Rate limit will reset at: %s`, resetTime)
}
}
}Features:
SQL Injection PreventionParameterized QueriesFile: All database operations use parameterized queries with placeholder syntax: query := `
INSERT INTO issues (number, owner, repo, title, body, state, author_login, ...)
VALUES (?, ?, ?, ?, ?, ?, ?, ...)
ON CONFLICT(number) DO UPDATE SET
title = excluded.title,
body = excluded.body,
...`
_, err := db.Exec(query,
issue.Number,
owner,
repo,
issue.Title,
// ... all parameters
)Verification:
Database Security ConfigurationFile: Foreign key constraints enabled for data integrity: if _, err := db.Exec("PRAGMA foreign_keys = ON;"); err != nil {
db.Close()
return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
}Schema Security:
Command Injection PreventionSafe Command ExecutionFile: Only one func getGhAuthToken() (string, error) {
cmd := exec.Command("gh", "auth", "token")
output, err := cmd.Output()
if err != nil {
return "", err
}
return stringTrim(output), nil
}Security Analysis:
Verification:
Input ValidationRepository Format ValidationFile: Proper validation of user-provided repository names: func isValidRepoFormat(repo string) bool {
parts := strings.SplitN(repo, "/", 2)
return len(parts) == 2 && parts[0] != "" && parts[1] != ""
}Validation Rules:
Usage in Setup: if !repoPattern.MatchString(repo) {
return "", fmt.Errorf("invalid repository format. Use owner/repo format")
}Error Handling Without Information LeakageFile: Error messages are informative but don't expose sensitive details: if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf(`invalid GitHub token.
Please check your token and update it:
1. For environment variable: export GITHUB_TOKEN=your_token
2. For config file: run 'ghissues config' to update
3. For GitHub CLI: run 'gh auth refresh'`)
}Best Practices:
HTTP SecurityRate Limit HandlingFile: Proper detection and reporting of GitHub API rate limits: if resp.StatusCode == http.StatusForbidden {
remaining := resp.Header.Get("X-RateLimit-Remaining")
if remaining == "0" {
resetTime := resp.Header.Get("X-RateLimit-Reset")
return fmt.Errorf("GitHub API rate limit exceeded, resets at %s", resetTime)
}
}Proper HTTP HeadersAll API requests include:
Context-Aware RequestsAll HTTP requests support cancellation via context: req, err := http.NewRequestWithContext(ctx, "GET", url, nil)Informational Findings ℹ️Finding #1: Terminal Rendering SecurityFile: Description: Code: func renderMarkdown(body string) string {
renderer, err := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
)
if err != nil {
return body // Fallback to raw text
}
rendered, err := renderer.Render(body)
if err != nil {
return body
}
return rendered
}Security Analysis:
Risk: LOW Finding #2: No Explicit Memory ClearingFile: Description: Observation: func GetToken() (string, TokenSource, error) {
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
return token, TokenSourceEnv, nil
// Token string persists in memory
}
}Context:
Risk: LOW Note: If maximum security required, consider using byte slices with explicit zeroing: tokenBytes := []byte(token)
defer func() {
for i := range tokenBytes {
tokenBytes[i] = 0
}
}()Finding #3: Database File PermissionsFile: Description: Current Behavior:
Observation: $ ls -l .ghissues.db
-rw-r--r--@ 1 shepbook staff 648K Jan 21 21:07 .ghissues.dbPermissions: 644 (owner read/write, group/others read) Risk: LOW Recommendation:
Optional Enhancement: // Set restrictive permissions on database file
if err := os.Chmod(dbPath, 0600); err != nil {
return nil, fmt.Errorf("failed to set database permissions: %w", err)
}Dependency SecurityDirect DependenciesAnalyzed from
Security AssessmentNo Critical Vulnerabilities Detected All dependencies are:
Recommendations
RecommendationsImmediate Actions (Required)
Short-Term Improvements (Recommended)
Long-Term Enhancements (Optional)
Files ReviewedCore Security Files
Additional Files
Configuration Files
ConclusionThe GitHub Issues TUI codebase demonstrates solid security fundamentals with strong practices in authentication, database security, and API communication. The codebase is well-structured and follows Go security best practices. Critical Issues: The two critical issues (missing .gitignore and committed database file) can be resolved quickly and should be addressed before the next commit. Overall Security Posture: After resolving the critical issues, the security posture would be rated as GOOD for a local TUI application handling GitHub data. Compliance: No regulatory compliance requirements identified (not a web service, local-only application). Risk Acceptance: The current plaintext token storage with restrictive file permissions (0600) is acceptable for a local CLI tool, though keychain integration would be a valuable enhancement. Review Metadata
End of Security Review For questions or concerns about this security review, please contact the security team or open an issue in the repository. |
This is an implementation using MiniMax M2.1 via synthetic.new and using claude code as the agent and ralph-tui as the "driver" of the agent.
The one shot got most of the visual part of the TUI nicely completed, however it also had issues with keybindings.
A second pass with claude code plan mode then got keybindings correctly working, though it still doesn't update the details pane when scrolling through issues.