Skip to content

Bug Fixes and QOL Improvements#1

Merged
hacktivist123 merged 9 commits intomainfrom
chore/bug-fixes
Jan 15, 2026
Merged

Bug Fixes and QOL Improvements#1
hacktivist123 merged 9 commits intomainfrom
chore/bug-fixes

Conversation

@hacktivist123
Copy link
Contributor

@hacktivist123 hacktivist123 commented Jan 15, 2026

Summary by CodeRabbit

  • New Features

    • Environments, agent tokens, rollback events, events/audit logs, soft-delete, auto-reconcile toggles, and structured drift reporting.
  • Bug Fixes

    • Safer non-interactive CLI confirmations, hardened external links, improved TLS/self-signed cert handling, and install-version edge-case fixed.
  • Refactor

    • Centralized logging, graceful shutdown/watch improvements, and standardized API error handling.
  • Documentation

    • Expanded CONTRIBUTING with testing, build/run, local dev, and Swagger regeneration guidance.
  • Tests

    • More reliable tests, fast-bcrypt helper, and cleanup/error-check improvements.

✏️ Tip: You can customize this high-level summary in your review settings.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 15, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
pullbase 8afafa3 Commit Preview URL

Branch Preview URL
Jan 15 2026, 09:09 PM

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Bug Fixes and QOL Improvements' is vague and generic, lacking specific details about the primary changes in this comprehensive pull request. Replace with a more specific title that identifies the main change, such as 'Replace slog with internal logging package and refactor API error handling' or 'Add drift details support and SQLite migration infrastructure'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hacktivist123 hacktivist123 added enhancement New feature or request documentation Improvements or additions to documentation labels Jan 15, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/pkg/configvalidate/validate.go (1)

92-109: Syntax error: Missing closing brace for the Decode error check.

The structure appears broken. Line 93 opens an if err != nil { block, but there's no closing brace before the error handling logic at line 94. The existing error handling code (lines 94-107) should be nested inside line 93's block, but the indentation and brace structure is incorrect.

The code should be structured as:

🐛 Proposed fix
 	var config ServerConfig
 	if len(rootNode.Content) > 0 {
 		if err := rootNode.Content[0].Decode(&config); err != nil {
-	if yamlErr, ok := err.(*yaml.TypeError); ok {
-		for _, e := range yamlErr.Errors {
-			errors = append(errors, ValidationError{
-				Field:   "",
-				Message: e,
-			})
+			if yamlErr, ok := err.(*yaml.TypeError); ok {
+				for _, e := range yamlErr.Errors {
+					errors = append(errors, ValidationError{
+						Field:   "",
+						Message: e,
+					})
+				}
+			} else {
+				errors = append(errors, ValidationError{
+					Field:   "",
+					Message: fmt.Sprintf("YAML parsing error: %v", err),
+				})
+			}
+			return errors
 		}
-	} else {
-		errors = append(errors, ValidationError{
-			Field:   "",
-			Message: fmt.Sprintf("YAML parsing error: %v", err),
-		})
-	}
-	return errors
-	}
 	}
server/docs/swagger.json (1)

9-13: Same missing securityDefinitions issue as swagger.yaml.

Multiple endpoints reference AgentAuth and BearerAuth security schemes that are no longer defined. If this file is auto-generated from annotations or swagger.yaml, ensure the security definitions are restored in the source.

Also applies to: 67-70, 113-116

🤖 Fix all issues with AI agents
In `@public/pullbase-website/src/pages/index.astro`:
- Around line 58-59: Anchor element for "[ GET_STARTED ]" includes rel="noopener
noreferrer" but lacks target="_blank"; either remove the rel attribute if you
want same-tab navigation or add target="_blank" to open in a new tab and keep
rel for security – update the <a> element that contains the text "[ GET_STARTED
]" in index.astro accordingly so rel and target attributes are consistent.
- Around line 35-37: The anchor with text "GET_STARTED" includes rel="noopener
noreferrer" but no target, causing inconsistency; update the <a> element (the
GET_STARTED link) to either remove the rel attribute if you intend same-tab
navigation, or—preferred for external docs—add target="_blank" so rel="noopener
noreferrer" is effective and matches other external links; ensure you update the
GET_STARTED link attributes accordingly.

In `@server/migrations/sqlite/000002_add_auto_reconcile.down.sql`:
- Around line 1-2: The migration uses "ALTER TABLE servers DROP COLUMN
auto_reconcile" which requires SQLite >= 3.35.0; either update the migration to
be compatible with older SQLite by performing the safe alter-table pattern
(create a new servers_temp table without auto_reconcile, copy data from servers,
drop servers, rename servers_temp to servers, and recreate indexes/constraints)
in 000002_add_auto_reconcile.down.sql (and the other listed migrations), or
alternatively document/enforce the minimum SQLite version (>= 3.35.0) for the
project and add a startup/version check; reference the migration name
"000002_add_auto_reconcile.down.sql" and the SQL statement "ALTER TABLE servers
DROP COLUMN auto_reconcile" when making the change.

In `@server/migrations/sqlite/000003_add_soft_delete.down.sql`:
- Around line 1-5: Add a note to CONTRIBUTING.md documenting the minimum SQLite
version required for migrations that use ALTER TABLE ... DROP COLUMN;
specifically add "SQLite 3.35.0+ (for development)" to the
prerequisites/dependencies section so readers know migrations like those
referencing DROP COLUMN (e.g., the migration that drops idx_servers_deleted_at
and uses ALTER TABLE servers DROP COLUMN deleted_at) require SQLite 3.35.0 or
later.

In `@server/migrations/sqlite/011_add_deployed_commit.down.sql`:
- Around line 1-3: Add a note to the migrations README stating the project
requires SQLite 3.35.0 or later because several migrations use the "ALTER TABLE
... DROP COLUMN" syntax; update server/migrations/README.md to include a brief
"Minimum SQLite version" section (or a line in the intro) that names 3.35.0+ and
references that migrations such as 000002, 000003, 013 and
011_add_deployed_commit.down.sql rely on DROP COLUMN so developers must run at
least that SQLite version.

In `@server/migrations/sqlite/017_replace_pat_with_github_app.up.sql`:
- Around line 6-7: The migration currently executes "ALTER TABLE environments
DROP COLUMN token" which permanently removes the token column and data; before
dropping it, add an explicit pre-migration step that exports/archives existing
environments.token values (e.g., to a backup table or external storage) and
implement a validation step that checks every environment has been migrated to
GitHub App auth (fail the migration and surface a clear warning if any
environments still rely on token), and update the down migration and release
notes to document the recovery/rollback path and communicate the required
upgrade steps to users.

In `@server/migrations/sqlite/021_add_notification_webhook.down.sql`:
- Line 1: The down migration uses "ALTER TABLE environments DROP COLUMN
notification_webhook_url;" which lacks the IF EXISTS defensive clause; update
the SQL to drop the column with IF EXISTS (i.e., ALTER TABLE environments DROP
COLUMN IF EXISTS notification_webhook_url) so it matches the PostgreSQL pattern
and avoids errors during partial rollbacks while still noting SQLite 3.35.0+ is
required.

In `@server/pkg/config/config_test.go`:
- Around line 89-92: The t.Cleanup closure captures the loop variable key by
reference so only the last env var gets unset; fix by capturing key (and/or
value) by value inside the loop before registering cleanup (e.g. create a new
local variable like k := key or use the shadow-variable pattern) and call
t.Cleanup(func() { os.Unsetenv(k) }) so each registered cleanup unsets the
correct environment variable that was set with os.Setenv.

In `@server/pkg/configvalidate/validate.go`:
- Around line 245-247: The error message in validate.go is misleading: the check
uses if mode > 0777 allowing 0777, but the returned error says "must be less
than 0777"; update the error text in the return fmt.Errorf call that references
modeStr so it correctly reflects the condition (e.g., "must be less than or
equal to 0777" or "must be at most 0777") while leaving the existing mode > 0777
check unchanged.
🧹 Nitpick comments (22)
server/migrations/sqlite/000001_create_initial_schema.up.sql (2)

13-22: Consider adding a UNIQUE constraint on server name or (name, repo_url).

If server names should be unique within the system (or unique per repository), adding a constraint would prevent duplicate entries at the database level. This is optional depending on your business requirements.


25-35: Consider adding an index on (server_id, agent_timestamp DESC) as a composite index.

You have separate indexes on server_id and agent_timestamp, but queries filtering by server_id and ordering by agent_timestamp (e.g., getting the latest status for a server) would benefit from a composite index.

♻️ Suggested composite index
 CREATE INDEX IF NOT EXISTS idx_agent_status_server_id ON agent_status(server_id);
-CREATE INDEX IF NOT EXISTS idx_agent_status_timestamp ON agent_status(agent_timestamp DESC);
+CREATE INDEX IF NOT EXISTS idx_agent_status_server_timestamp ON agent_status(server_id, agent_timestamp DESC);
server/migrations/sqlite/015_add_agent_tokens.up.sql (1)

14-15: Redundant index on token_hash.

The UNIQUE constraint on token_hash (line 4) already creates an implicit unique index in SQLite. The explicit index idx_agent_tokens_token_hash is redundant and adds unnecessary overhead during writes.

♻️ Suggested fix
 -- Create index for efficient token lookups
-CREATE INDEX idx_agent_tokens_token_hash ON agent_tokens(token_hash);
 CREATE INDEX idx_agent_tokens_server_id ON agent_tokens(server_id);
 CREATE INDEX idx_agent_tokens_active ON agent_tokens(is_active) WHERE is_active = 1;

If you remove this index from the up migration, also remove DROP INDEX IF EXISTS idx_agent_tokens_token_hash; from the down migration.

CONTRIBUTING.md (3)

31-33: LGTM! Clear PR guidelines with helpful cross-references.

The updated guidance properly references the new Testing section and clarifies documentation expectations.

Optional: Consider wrapping the bare URL on Line 32 in angle brackets per Markdown best practices: <https://github.com/pullbase/docs>


58-77: Excellent addition! Comprehensive testing guidance.

The new Testing section clearly distinguishes between sanity, full, and integration tests, helping contributors choose the appropriate test level.

Optional: Line 66 has a minor grammar quirk. Consider rewording for clarity:

📝 Suggested grammar improvement
-Full tests (requires built UI assets for server without stubs):
+Full tests (requires UI assets to be built for server without stubs):

Or more concisely:

-Full tests (requires built UI assets for server without stubs):
+Full tests (need built UI assets, no stubs):

79-85: Great addition! Clear guidance for documentation and API changes.

The new section provides essential instructions for keeping documentation and Swagger artifacts up-to-date.

Optional: Line 81 has a bare URL. Consider wrapping it in angle brackets per Markdown conventions: <https://github.com/pullbase/docs>

server/cmd/pullbasectl/status.go (1)

125-138: Good signal handling pattern.

The context-based cancellation with signal notification is well implemented. The buffered channel prevents the signal from being lost if not immediately received.

Minor note: signal.Stop(sigCh) could be called in the deferred cleanup for completeness, though it's not critical for a CLI tool that exits after this function returns.

♻️ Optional: Add signal cleanup
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
 	sigCh := make(chan os.Signal, 1)
 	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
+	defer signal.Stop(sigCh)
 	go func() {
 		<-sigCh
 		cancel()
 	}()
server/migrations/sqlite/014_add_auto_reconcile_to_environments.up.sql (1)

5-5: Consider adding IF NOT EXISTS for idempotent index creation.

Other migrations in this PR (e.g., 010_create_events.up.sql) use IF NOT EXISTS for table creation. For consistency and to allow safe re-runs, consider adding the same guard to the index creation.

Additionally, an index on a low-cardinality boolean column (0/1) typically provides minimal query optimization benefit unless you're frequently querying for rare values.

Suggested improvement
--- Create index for auto_reconcile column for performance
-CREATE INDEX idx_environments_auto_reconcile ON environments(auto_reconcile);
+-- Create index for auto_reconcile column for performance
+CREATE INDEX IF NOT EXISTS idx_environments_auto_reconcile ON environments(auto_reconcile);
server/migrations/sqlite/010_create_events.up.sql (1)

12-16: Index creation missing IF NOT EXISTS guard.

The table creation uses IF NOT EXISTS for idempotency, but the index creation statements do not. If this migration is partially applied or re-run, the indexes could fail to create or cause errors.

Suggested fix for idempotent index creation
 -- Create indexes for efficient querying
-CREATE INDEX idx_events_environment_id ON events(environment_id);
-CREATE INDEX idx_events_server_id ON events(server_id);
-CREATE INDEX idx_events_event_type ON events(event_type);
-CREATE INDEX idx_events_timestamp ON events(timestamp DESC);
+CREATE INDEX IF NOT EXISTS idx_events_environment_id ON events(environment_id);
+CREATE INDEX IF NOT EXISTS idx_events_server_id ON events(server_id);
+CREATE INDEX IF NOT EXISTS idx_events_event_type ON events(event_type);
+CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp DESC);
server/migrations/sqlite/008_create_environments.up.sql (1)

18-18: Low-value index on provider column.

Since the CHECK constraint enforces provider = 'github' (single allowed value), this index will have extremely low cardinality and provide no query benefit. Consider removing it unless you plan to support multiple providers in the future.

♻️ Suggested removal
 CREATE INDEX idx_environments_repo_url ON environments(repo_url);
-CREATE INDEX idx_environments_provider ON environments(provider);
 CREATE INDEX idx_environments_status ON environments(status);
server/migrations/sqlite/021_add_notification_webhook.up.sql (1)

1-1: LGTM!

Adding a nullable TEXT column for webhook URLs is safe and appropriate.

Minor note: Other migrations in this PR (e.g., 011_add_deployed_commit.up.sql) include a descriptive comment header. Consider adding one for consistency, though this is optional.

server/migrations/sqlite/009_add_rollback_events.up.sql (1)

17-20: Consider adding IF NOT EXISTS to index creation for idempotency.

The table uses CREATE TABLE IF NOT EXISTS for safe re-runs, but the indexes use plain CREATE INDEX. If the migration partially succeeds (table created, some indexes created) and is retried, it will fail on the already-created indexes.

♻️ Suggested fix
 -- Create indexes for efficient querying
-CREATE INDEX idx_rollback_events_environment_id ON rollback_events(environment_id);
-CREATE INDEX idx_rollback_events_status ON rollback_events(status);
-CREATE INDEX idx_rollback_events_created_at ON rollback_events(created_at DESC);
+CREATE INDEX IF NOT EXISTS idx_rollback_events_environment_id ON rollback_events(environment_id);
+CREATE INDEX IF NOT EXISTS idx_rollback_events_status ON rollback_events(status);
+CREATE INDEX IF NOT EXISTS idx_rollback_events_created_at ON rollback_events(created_at DESC);
server/migrations/sqlite/012_remove_polling_support.down.sql (1)

1-3: Clarify that data changes are also not reverted.

The comment only mentions the CHECK constraint limitation, but doesn't clarify that the status = 'fallback''error' data change from the up migration is also not reversed. This could mislead developers.

📝 Suggested improvement
 -- Migration: Restore polling support and fallback status
 -- Note: SQLite cannot modify CHECK constraints after table creation.
--- This is a no-op for SQLite.
+-- This is a no-op for SQLite. The data change (fallback -> error) from the
+-- up migration is not reverted as we cannot reliably identify which 'error'
+-- statuses were originally 'fallback'.
server/pkg/testutil/testutil.go (1)

156-166: Consider documenting the path assumption.

The function traverses up 3 directory levels from testutil.go to reach server/migrations/. This works correctly for the current structure but could break if the test utility file is moved.

A brief comment documenting the expected path relationship would aid future maintainers:

📝 Suggested documentation
 func defaultMigrationPath() string {
+	// Navigate from server/pkg/testutil/testutil.go up to server/migrations/
 	_, currentFile, _, ok := runtime.Caller(0)
 	if !ok {
 		return "file://migrations"
 	}
 
 	serverDir := filepath.Dir(filepath.Dir(filepath.Dir(currentFile)))
 	migrationsDir := filepath.Join(serverDir, "migrations")
 
 	return "file://" + migrationsDir
 }
server/pkg/apierrors/errors.go (1)

300-301: Consider handling JSON encoding errors.

The json.NewEncoder(w).Encode(resp) error is silently ignored. While this is common practice for response writing (since headers are already sent), consider logging encoding failures for debugging purposes.

♻️ Optional: Log encoding errors
-	json.NewEncoder(w).Encode(resp)
+	if err := json.NewEncoder(w).Encode(resp); err != nil {
+		// Log but don't return - headers already sent
+		// Consider adding: log.Warn("failed to encode error response", "error", err)
+	}
server/pkg/rollback/service_test.go (1)

201-201: Consider using io.Discard instead of os.Stdout for quieter test output.

The logger is initialized with os.Stdout, which will produce output during test runs. Other test files in this PR (e.g., webhook_handlers_integration_test.go) use io.Discard to suppress log output. Consider aligning for consistency and cleaner test output.

Suggested change
+import "io"
+
-logger := logging.NewLogger(logging.Options{Format: "text", Output: os.Stdout})
+logger := logging.NewLogger(logging.Options{Format: "text", Output: io.Discard})

Also applies to: 337-337, 364-364, 400-400

server/pkg/notifications/webhook_test.go (1)

127-148: Test correctly validates context timeout during retry backoff.

The test flow is accurate: first request returns 500 → retry backoff begins (50ms) → context times out (25ms) → ctx.Err() returned via the select statement. The implementation properly checks ctx.Done() during the backoff wait, so cancellation is correctly handled.

The timing margins are intentionally tight (25ms timeout vs 50ms backoff) to ensure the context expires during the wait period rather than before. This is a valid test scenario. However, in loaded CI environments where the initial HTTP request itself may take measurable time, the timing could be tighter than intended, potentially causing the test to be sensitive to execution speed.

Consider either increasing the context timeout slightly (e.g., 40ms) while keeping it below the 50ms backoff, or reducing the backoff delay to create a clearer separation and more predictable behavior across environments.

server/pkg/server/handlers_agent.go (2)

241-247: Move defer r.Body.Close() after reading the body.

The defer r.Body.Close() is placed before json.NewDecoder(r.Body).Decode(). While this will technically work (defer executes at function return), it's unconventional and could cause issues if the code is refactored to return early. The standard pattern is to close the body after you're done reading it.

♻️ Suggested improvement
 func (api *API) UpdateAgentStatusHandler(w http.ResponseWriter, r *http.Request) {

 	claims, ok := GetUserClaims(r.Context())
 	if !ok || claims == nil {
 		writeAPIError(w, apierrors.Unauthorized("Authorization header or session cookie required"))
 		return
 	}

 	serverID := chi.URLParam(r, "serverID")
 	var payload AgentStatusPayload
-	defer r.Body.Close()

 	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+		r.Body.Close()
 		api.log().Error("bad request for status update", "server_id", serverID, "error", err)
 		writeAPIError(w, apierrors.BadRequestf("Invalid status payload: %s", err.Error()))
 		return
 	}
+	r.Body.Close()

Alternatively, keep defer but place it immediately after acquiring the body or at function start:

 func (api *API) UpdateAgentStatusHandler(w http.ResponseWriter, r *http.Request) {
+	defer r.Body.Close()

 	claims, ok := GetUserClaims(r.Context())

527-533: Same issue: move defer r.Body.Close() to function start.

Same pattern issue as UpdateAgentStatusHandler. Consider moving the defer to the beginning of the function for clarity.

♻️ Suggested improvement
 func (api *API) AgentUpdateStatusHandler(w http.ResponseWriter, r *http.Request) {
+	defer r.Body.Close()

 	agentToken, ok := GetAgentToken(r.Context())
 	if !ok || agentToken == nil {
 		writeAPIError(w, apierrors.Unauthorized("Agent token required"))
 		return
 	}

 	serverID := agentToken.ServerID
 	var payload AgentStatusPayload
-	defer r.Body.Close()
server/cmd/server/main.go (1)

280-284: Consider consolidating duplicate logger instances.

Two separate logger instances are created with identical options (Format: "text", Output: os.Stdout). Consider creating a single logger instance and reusing it.

♻️ Suggested consolidation
+	logger := logging.NewLogger(logging.Options{Format: "text", Output: os.Stdout})
+
 	var environmentMonitor *gitmonitor.EnvironmentMonitor
 	var rollbackGitMonitor rollback.GitMonitor
 	var installationTokenProvider gitmonitor.InstallationTokenProvider
 	if cfg.Git.Enabled {
 		// ... encryption key and GitHub client setup ...

-		logger := logging.NewLogger(logging.Options{Format: "text", Output: os.Stdout})
 		webhookRouter := gitmonitor.NewWebhookRouter(logger, nil)
 		webhookManager := gitmonitor.NewWebhookManager(webhookRouter, logger, envRepo)
 		// ...
 	}

-	logger := logging.NewLogger(logging.Options{Format: "text", Output: os.Stdout})
 	rollbackService := rollback.NewService(repo, rollbackGitMonitor, logger)

This would require moving the logger declaration before the if cfg.Git.Enabled block and ensuring it's in scope for both use cases.

Also applies to: 302-303

server/docs/swagger.yaml (1)

481-482: Stripped API metadata may impact documentation quality.

The info section has been reduced to just contact: {}. While this compiles, consider retaining essential metadata like title, version, and description for better API documentation and client SDK generation.

server/docs/swagger.json (1)

3-5: Stripped API metadata.

Similar to swagger.yaml, the info block has minimal content. If this is auto-generated, the source annotations should include proper API metadata.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50519a8 and b8f9081.

⛔ Files ignored due to path filters (1)
  • server/go.sum is excluded by !**/*.sum
📒 Files selected for processing (117)
  • CONTRIBUTING.md
  • agent/main.go
  • public/pullbase-website/src/pages/index.astro
  • server/cmd/pullbasectl/auth.go
  • server/cmd/pullbasectl/bootstrap.go
  • server/cmd/pullbasectl/environments.go
  • server/cmd/pullbasectl/globals.go
  • server/cmd/pullbasectl/servers.go
  • server/cmd/pullbasectl/status.go
  • server/cmd/pullbasectl/users.go
  • server/cmd/pullbasectl/validate_config_test.go
  • server/cmd/server/main.go
  • server/cmd/server/main_test.go
  • server/docs/docs.go
  • server/docs/swagger.json
  • server/docs/swagger.yaml
  • server/go.mod
  • server/migrations/postgres/000001_create_initial_schema.down.sql
  • server/migrations/postgres/000001_create_initial_schema.up.sql
  • server/migrations/postgres/000002_add_auto_reconcile.down.sql
  • server/migrations/postgres/000002_add_auto_reconcile.up.sql
  • server/migrations/postgres/000003_add_soft_delete.down.sql
  • server/migrations/postgres/000003_add_soft_delete.up.sql
  • server/migrations/postgres/008_create_environments.down.sql
  • server/migrations/postgres/008_create_environments.up.sql
  • server/migrations/postgres/009_add_rollback_events.down.sql
  • server/migrations/postgres/009_add_rollback_events.up.sql
  • server/migrations/postgres/010_create_events.down.sql
  • server/migrations/postgres/010_create_events.up.sql
  • server/migrations/postgres/011_add_deployed_commit.down.sql
  • server/migrations/postgres/011_add_deployed_commit.up.sql
  • server/migrations/postgres/012_remove_polling_support.down.sql
  • server/migrations/postgres/012_remove_polling_support.up.sql
  • server/migrations/postgres/013_add_environment_to_servers.down.sql
  • server/migrations/postgres/013_add_environment_to_servers.up.sql
  • server/migrations/postgres/014_add_auto_reconcile_to_environments.down.sql
  • server/migrations/postgres/014_add_auto_reconcile_to_environments.up.sql
  • server/migrations/postgres/015_add_agent_tokens.down.sql
  • server/migrations/postgres/015_add_agent_tokens.up.sql
  • server/migrations/postgres/016_remove_server_git_fields.down.sql
  • server/migrations/postgres/016_remove_server_git_fields.up.sql
  • server/migrations/postgres/017_replace_pat_with_github_app.down.sql
  • server/migrations/postgres/017_replace_pat_with_github_app.up.sql
  • server/migrations/postgres/018_restrict_providers_to_github.down.sql
  • server/migrations/postgres/018_restrict_providers_to_github.up.sql
  • server/migrations/postgres/019_add_branch_and_deploy_path_to_environments.down.sql
  • server/migrations/postgres/019_add_branch_and_deploy_path_to_environments.up.sql
  • server/migrations/postgres/020_add_agent_version.down.sql
  • server/migrations/postgres/020_add_agent_version.up.sql
  • server/migrations/postgres/021_add_notification_webhook.down.sql
  • server/migrations/postgres/021_add_notification_webhook.up.sql
  • server/migrations/postgres/022_add_drift_details.down.sql
  • server/migrations/postgres/022_add_drift_details.up.sql
  • server/migrations/sqlite/000001_create_initial_schema.down.sql
  • server/migrations/sqlite/000001_create_initial_schema.up.sql
  • server/migrations/sqlite/000002_add_auto_reconcile.down.sql
  • server/migrations/sqlite/000002_add_auto_reconcile.up.sql
  • server/migrations/sqlite/000003_add_soft_delete.down.sql
  • server/migrations/sqlite/000003_add_soft_delete.up.sql
  • server/migrations/sqlite/008_create_environments.down.sql
  • server/migrations/sqlite/008_create_environments.up.sql
  • server/migrations/sqlite/009_add_rollback_events.down.sql
  • server/migrations/sqlite/009_add_rollback_events.up.sql
  • server/migrations/sqlite/010_create_events.down.sql
  • server/migrations/sqlite/010_create_events.up.sql
  • server/migrations/sqlite/011_add_deployed_commit.down.sql
  • server/migrations/sqlite/011_add_deployed_commit.up.sql
  • server/migrations/sqlite/012_remove_polling_support.down.sql
  • server/migrations/sqlite/012_remove_polling_support.up.sql
  • server/migrations/sqlite/013_add_environment_to_servers.down.sql
  • server/migrations/sqlite/013_add_environment_to_servers.up.sql
  • server/migrations/sqlite/014_add_auto_reconcile_to_environments.down.sql
  • server/migrations/sqlite/014_add_auto_reconcile_to_environments.up.sql
  • server/migrations/sqlite/015_add_agent_tokens.down.sql
  • server/migrations/sqlite/015_add_agent_tokens.up.sql
  • server/migrations/sqlite/016_remove_server_git_fields.down.sql
  • server/migrations/sqlite/016_remove_server_git_fields.up.sql
  • server/migrations/sqlite/017_replace_pat_with_github_app.down.sql
  • server/migrations/sqlite/017_replace_pat_with_github_app.up.sql
  • server/migrations/sqlite/018_restrict_providers_to_github.down.sql
  • server/migrations/sqlite/018_restrict_providers_to_github.up.sql
  • server/migrations/sqlite/019_add_branch_and_deploy_path_to_environments.down.sql
  • server/migrations/sqlite/019_add_branch_and_deploy_path_to_environments.up.sql
  • server/migrations/sqlite/020_add_agent_version.down.sql
  • server/migrations/sqlite/020_add_agent_version.up.sql
  • server/migrations/sqlite/021_add_notification_webhook.down.sql
  • server/migrations/sqlite/021_add_notification_webhook.up.sql
  • server/migrations/sqlite/022_add_drift_details.down.sql
  • server/migrations/sqlite/022_add_drift_details.up.sql
  • server/pkg/apierrors/errors.go
  • server/pkg/auth/auth.go
  • server/pkg/config/config.go
  • server/pkg/config/config_test.go
  • server/pkg/configvalidate/validate.go
  • server/pkg/database/database.go
  • server/pkg/database/repository.go
  • server/pkg/database/repository_test.go
  • server/pkg/gitmonitor/environment_monitor.go
  • server/pkg/gitmonitor/environment_monitor_test.go
  • server/pkg/gitmonitor/webhook_router.go
  • server/pkg/logging/logger.go
  • server/pkg/models/models.go
  • server/pkg/notifications/webhook.go
  • server/pkg/notifications/webhook_test.go
  • server/pkg/rollback/integration_test.go
  • server/pkg/rollback/service.go
  • server/pkg/rollback/service_test.go
  • server/pkg/server/handlers.go
  • server/pkg/server/handlers_agent.go
  • server/pkg/server/handlers_auth.go
  • server/pkg/server/handlers_config.go
  • server/pkg/server/handlers_environments.go
  • server/pkg/server/handlers_servers.go
  • server/pkg/server/handlers_test.go
  • server/pkg/server/webhook_handlers.go
  • server/pkg/server/webhook_handlers_integration_test.go
  • server/pkg/testutil/testutil.go
💤 Files with no reviewable changes (1)
  • server/cmd/pullbasectl/globals.go
🧰 Additional context used
🧬 Code graph analysis (19)
server/pkg/server/webhook_handlers_integration_test.go (1)
server/pkg/logging/logger.go (2)
  • NewLogger (29-47)
  • Options (23-27)
server/cmd/pullbasectl/auth.go (1)
server/pkg/auth/auth.go (1)
  • BootstrapPasswordMinLength (15-15)
server/pkg/server/webhook_handlers.go (2)
server/pkg/gitmonitor/environment_monitor.go (1)
  • EnvironmentMonitor (21-32)
server/pkg/logging/logger.go (1)
  • Logger (11-11)
server/pkg/notifications/webhook_test.go (2)
server/pkg/notifications/webhook.go (2)
  • NewServiceWithConfig (59-68)
  • BuildTestPayload (201-209)
server/pkg/logging/logger.go (1)
  • Fatal (104-107)
server/cmd/pullbasectl/users.go (1)
server/pkg/auth/auth.go (1)
  • BootstrapPasswordMinLength (15-15)
server/pkg/server/handlers_servers.go (1)
server/pkg/models/models.go (1)
  • DriftDetails (74-79)
server/pkg/config/config.go (1)
server/pkg/logging/logger.go (2)
  • Info (92-94)
  • Warn (96-98)
server/pkg/rollback/integration_test.go (1)
server/pkg/logging/logger.go (2)
  • NewLogger (29-47)
  • Options (23-27)
server/pkg/gitmonitor/webhook_router.go (3)
server/pkg/models/models.go (1)
  • GitProvider (144-144)
server/pkg/logging/logger.go (1)
  • Logger (11-11)
server/pkg/gitmonitor/environment_monitor.go (1)
  • EnvironmentMonitor (21-32)
server/pkg/database/repository.go (1)
server/pkg/logging/logger.go (3)
  • Info (92-94)
  • Warn (96-98)
  • Debug (88-90)
server/pkg/rollback/service.go (1)
server/pkg/logging/logger.go (1)
  • Logger (11-11)
server/pkg/database/database.go (1)
server/pkg/logging/logger.go (1)
  • Info (92-94)
server/pkg/rollback/service_test.go (1)
server/pkg/logging/logger.go (2)
  • NewLogger (29-47)
  • Options (23-27)
server/pkg/gitmonitor/environment_monitor.go (1)
server/pkg/logging/logger.go (1)
  • Logger (11-11)
server/pkg/server/handlers.go (1)
server/pkg/logging/logger.go (3)
  • Logger (11-11)
  • Default (64-66)
  • Warn (96-98)
server/pkg/server/handlers_auth.go (5)
server/pkg/apierrors/errors.go (9)
  • Error (86-91)
  • Gone (244-249)
  • Validationf (188-193)
  • Forbidden (167-172)
  • BadRequest (195-200)
  • Internal (223-228)
  • Is (328-334)
  • NotFoundf (146-151)
  • Unauthorized (153-158)
server/pkg/auth/auth.go (1)
  • BootstrapPasswordMinLength (15-15)
server/pkg/database/repository.go (1)
  • ErrNotFound (20-20)
server/pkg/models/models.go (1)
  • RoleAdmin (184-184)
server/pkg/logging/logger.go (1)
  • Warn (96-98)
server/cmd/server/main.go (1)
server/pkg/logging/logger.go (4)
  • Warn (96-98)
  • Info (92-94)
  • NewLogger (29-47)
  • Options (23-27)
server/pkg/notifications/webhook.go (1)
server/pkg/logging/logger.go (3)
  • Logger (11-11)
  • NewLogger (29-47)
  • Options (23-27)
server/pkg/testutil/testutil.go (1)
server/pkg/logging/logger.go (1)
  • Warn (96-98)
🪛 Checkov (3.2.334)
server/docs/swagger.yaml

[medium] 74-78: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🪛 LanguageTool
CONTRIBUTING.md

[style] ~66-~66: The double modal “requires built” is nonstandard (only accepted in certain dialects). Consider “to be built”.
Context: ...gs=test ./... ``` Full tests (requires built UI assets for server without stubs): `...

(NEEDS_FIXED)

🪛 markdownlint-cli2 (0.18.1)
CONTRIBUTING.md

32-32: Bare URL used

(MD034, no-bare-urls)


81-81: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: unit-tests
  • GitHub Check: Workers Builds: pullbase

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

hacktivist123 and others added 2 commits January 15, 2026 08:08
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@CONTRIBUTING.md`:
- Around line 81-88: Update the swag init command in CONTRIBUTING.md to use the
correct project paths: replace the generator entry flag value `-g
cmd/server/main.go` with `-g server/cmd/server/main.go` and replace the output
directory flag `-o docs` with `-o server/docs` so the command matches the actual
entry point and docs location used by the project.
- Around line 60-79: In the "Testing" section fix the grammar of the quick test
description by updating the line "Quick sanity (uses stubs to avoid UI embed):"
to a clearer phrase such as "Quick sanity check (uses stubs to avoid embedding
the UI):" (locate the "Testing" header and that sentence to make the change).
🧹 Nitpick comments (5)
CONTRIBUTING.md (1)

32-32: Consider wrapping bare URLs in angle brackets.

Static analysis flagged bare URLs at lines 32 and 83. For markdown best practices, consider wrapping them in angle brackets.

📝 Proposed formatting improvements

Line 32:

-5. Update documentation if the change alters behavior or adds features (docs live at https://github.com/pullbase/docs)
+5. Update documentation if the change alters behavior or adds features (docs live at <https://github.com/pullbase/docs>)

Line 83:

-- Update relevant docs in https://github.com/pullbase/docs when features or behavior change.
+- Update relevant docs in <https://github.com/pullbase/docs> when features or behavior change.

Also applies to: 83-83

server/pkg/server/handlers.go (1)

268-278: Consider using instance logger for consistency.

The requireRole function uses package-level logging.Error and logging.Warn, which write to the default logger. However, other methods in this file (e.g., RecordAuditLog at lines 297, 312) use api.log() to respect a custom logger if configured.

Since requireRole doesn't have access to the API instance, this is acceptable, but worth noting that log output from role checks will always go to the default logger regardless of API.Logger configuration.

server/cmd/server/main.go (1)

279-283: Consider reusing the logger instance.

A logger is created here at line 279, and another identical one is created at line 301. Both use the same options (Format: "text", Output: os.Stdout). Consider creating the logger once earlier and reusing it.

♻️ Suggested refactor

Create the logger once before the if cfg.Git.Enabled block:

+	logger := logging.NewLogger(logging.Options{Format: "text", Output: os.Stdout})
+
 	var environmentMonitor *gitmonitor.EnvironmentMonitor
 	var rollbackGitMonitor rollback.GitMonitor
 	var installationTokenProvider gitmonitor.InstallationTokenProvider
 	if cfg.Git.Enabled {
 		// ... existing code ...
-		logger := logging.NewLogger(logging.Options{Format: "text", Output: os.Stdout})
 		webhookRouter := gitmonitor.NewWebhookRouter(logger, nil)

And remove the duplicate creation at line 301.

server/pkg/testutil/testutil.go (2)

155-165: Path resolution may be fragile in some test execution contexts.

defaultMigrationPath() uses runtime.Caller(0) to locate the source file and navigates up three directories to find migrations. This works when running tests from the standard Go toolchain but may fail if:

  • Tests are run from a different working directory
  • The binary is compiled and run separately from source

The fallback to "file://migrations" at line 158-159 provides a safety net. Consider documenting this behavior or adding a test to verify the path resolution.


334-340: Hardcoded migration version may require manual updates.

The fallback schema inserts migration version 22 as the current version. This needs to be updated whenever new migrations are added, which is easy to forget.

Consider extracting this as a constant or documenting the maintenance requirement.

♻️ Suggested refactor
+const fallbackSchemaVersion = 22 // Update when adding new migrations

 // In setupFallbackSchema():
-		INSERT INTO schema_migrations (version, dirty) VALUES (22, false) ON CONFLICT DO NOTHING;
+		INSERT INTO schema_migrations (version, dirty) VALUES (%d, false) ON CONFLICT DO NOTHING;

Or add a comment near the INSERT statement:

-- NOTE: Update this version when adding new migrations
INSERT INTO schema_migrations (version, dirty) VALUES (22, false) ON CONFLICT DO NOTHING;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d90bd4c and 681a8ab.

📒 Files selected for processing (10)
  • CONTRIBUTING.md
  • server/cmd/server/main.go
  • server/migrations/sqlite/012_remove_polling_support.up.sql
  • server/pkg/configvalidate/validate.go
  • server/pkg/csrf/csrf.go
  • server/pkg/database/database.go
  • server/pkg/server/handlers.go
  • server/pkg/server/handlers_auth.go
  • server/pkg/server/routes.go
  • server/pkg/testutil/testutil.go
💤 Files with no reviewable changes (1)
  • server/pkg/csrf/csrf.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/pkg/configvalidate/validate.go
  • server/pkg/server/handlers_auth.go
  • server/migrations/sqlite/012_remove_polling_support.up.sql
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-15T20:03:09.042Z
Learnt from: hacktivist123
Repo: pullbase/pullbase PR: 1
File: server/pkg/config/config_test.go:89-92
Timestamp: 2026-01-15T20:03:09.042Z
Learning: Go 1.22+ fixes: range loop variables are captured per iteration, so closures (including t.Cleanup) generally don’t require shadowing or explicit capture. Since server/go.mod specifies Go 1.24.0, reviewers can assume this behavior across Go files in the repo and avoid shadowing for range variables in tests and implementation. If there’s code intended for older Go versions, consider explicit capture to preserve pre-1.22 semantics.

Applied to files:

  • server/pkg/server/routes.go
  • server/pkg/database/database.go
  • server/pkg/testutil/testutil.go
  • server/pkg/server/handlers.go
  • server/cmd/server/main.go
🧬 Code graph analysis (4)
server/pkg/database/database.go (1)
server/pkg/logging/logger.go (3)
  • Info (92-94)
  • Warn (96-98)
  • Debug (88-90)
server/pkg/testutil/testutil.go (2)
server/pkg/logging/logger.go (1)
  • Warn (96-98)
server/pkg/database/repository.go (1)
  • NewRepository (39-44)
server/pkg/server/handlers.go (1)
server/pkg/logging/logger.go (3)
  • Logger (11-11)
  • Default (64-66)
  • Warn (96-98)
server/cmd/server/main.go (1)
server/pkg/logging/logger.go (4)
  • Warn (96-98)
  • Info (92-94)
  • NewLogger (29-47)
  • Options (23-27)
🪛 LanguageTool
CONTRIBUTING.md

[style] ~68-~68: The double modal “requires built” is nonstandard (only accepted in certain dialects). Consider “to be built”.
Context: ...gs=test ./... ``` Full tests (requires built UI assets for server without stubs): `...

(NEEDS_FIXED)

🪛 markdownlint-cli2 (0.18.1)
CONTRIBUTING.md

32-32: Bare URL used

(MD034, no-bare-urls)


83-83: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: integration-tests
  • GitHub Check: Workers Builds: pullbase
🔇 Additional comments (31)
CONTRIBUTING.md (3)

31-33: Enhanced PR workflow looks good!

The additions explicitly require test verification and documentation updates, which improves contributor guidance and ensures quality standards.


42-44: Clear SQLite version requirement with good rationale.

The specific version requirement (3.35.0+) is well-justified by the note explaining the need for ALTER TABLE ... DROP COLUMN support in down migrations.


56-58: Agent build instructions are helpful.

The addition of agent build steps provides complete build guidance for contributors.

server/pkg/server/handlers.go (3)

16-16: LGTM!

Import updated to use the internal logging package, aligning with the PR-wide logging migration.


70-70: LGTM!

Logger field type updated to *logging.Logger. Since logging.Logger is an alias for slog.Logger (per server/pkg/logging/logger.go line 10), this maintains compatibility while centralizing the logging abstraction.


83-88: LGTM!

The log() helper correctly returns the internal logging type and falls back to logging.Default() when no logger is configured.

server/pkg/server/routes.go (1)

70-70: LGTM!

X-CSRF-Token correctly removed from AllowedHeaders, consistent with the CSRF removal across the codebase. The remaining headers (Accept, Authorization, Content-Type) cover standard API needs.

server/pkg/database/database.go (9)

8-12: LGTM!

Imports updated appropriately for the new logging package and strings for version parsing.


76-77: LGTM!

Logging updated to use the internal logging package.


111-116: LGTM!

Good addition of SQLite version validation. Logging a warning rather than failing allows flexibility while alerting operators to potential migration issues.


139-155: LGTM!

Version parsing handles the common major.minor.patch format correctly. Silently ignoring patch parse errors on line 152 is acceptable since patch version isn't used in the minimum version comparison.


163-171: LGTM!

InitSchema correctly delegates to migrationPathForDialect before passing to dialect-specific initializers.


174-188: LGTM!

migrationPathForDialect gracefully handles the file:// prefix and falls back to the original path if no dialect-specific subdirectory exists. The os.Stat check ensures the directory exists before modifying the path.


205-206: LGTM!

Logging updated to use the internal logging package.


224-225: LGTM!

Logging updated to use the internal logging package.


119-137: SQLite version requirement is correctly enforced.

The check for SQLite >= 3.35.0 is accurate. ALTER TABLE DROP COLUMN support was introduced in SQLite 3.35.0 (released March 12, 2021), making this the appropriate minimum version requirement for the migration system.

server/cmd/server/main.go (7)

32-32: LGTM!

Import updated to use the internal logging package.


65-65: LGTM!

Logging call updated to use the internal logging package.


186-197: LGTM!

Development certificate generation path logs appropriately with useful metadata (paths, validity, expiration).


314-322: LGTM!

API struct initialization correctly uses the logger and removes CSRF field (as per AI summary). All required fields are properly initialized.


421-448: LGTM!

Enhanced TLS certificate handling with clear error messages. The auto-generation of self-signed certificates when both are missing provides a good developer experience, while properly failing when only one file is present (indicating a configuration issue).


455-461: LGTM!

Helpful logging metadata added for TLS startup, including certificate modification times which aids debugging certificate rotation issues.


464-474: LGTM!

Graceful shutdown handling with appropriate logging.

server/pkg/testutil/testutil.go (8)

7-8: LGTM!

Imports added for the new functionality: filepath and runtime for path computation, database and logging packages.

Also applies to: 14-15


24-24: LGTM!

DefaultMigrationPath updated to a simpler relative path. The actual path resolution is handled by defaultMigrationPath().


31-38: LGTM!

TestDB struct expanded with Config and Dialect fields, enabling tests to access database configuration details. The unexported cleanup and t fields properly encapsulate test lifecycle management.


74-77: LGTM!

Using wait.ForListeningPort("5432/tcp") is more reliable than log-based waiting for PostgreSQL container readiness.


115-117: LGTM!

Logging updated to use the internal logging package for container termination warnings.


140-153: LGTM!

MustMigrate now falls back to defaultMigrationPath() when empty and logs the migration path being used, aiding test debugging.


392-401: LGTM!

Using t.Cleanup(cancel) for context cancellation is the correct pattern for Go 1.24.0, ensuring contexts are properly cleaned up when tests complete. Based on learnings, Go 1.22+ handles closure captures correctly.


356-360: No action needed. WaitForDBConnection is properly defined as a method on TestDB in server/pkg/testutil/helpers.go:323 and is correctly accessible from the call site in testutil.go.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/cmd/server/main.go`:
- Around line 433-434: After generating the self-signed certs, the os.Stat calls
for certFile and keyFile ignore errors and may leave certInfo or keyInfo nil;
update the code around the os.Stat(certFile) and os.Stat(keyFile) calls to check
and handle the returned errors (e.g., if err != nil) before later using
certInfo.ModTime() or keyInfo.ModTime(): log the stat error with context
(including certFile/keyFile), and return or exit the function with an error
instead of proceeding, ensuring no nil pointer dereference occurs.
♻️ Duplicate comments (1)
CONTRIBUTING.md (1)

68-68: Minor grammar issue: "requires built" is nonstandard.

Consider rewording to "requires UI assets to be built" or simply "requires built UI assets".

🧹 Nitpick comments (6)
CONTRIBUTING.md (1)

81-88: LGTM - swag paths are now correct.

The swag init command now correctly references server/cmd/server/main.go and outputs to server/docs, matching the actual project structure.

Static analysis flags bare URLs on lines 32 and 83. Consider using markdown link syntax for consistency:

  • [pullbase/docs](https://github.com/pullbase/docs)
server/pkg/testutil/testutil.go (3)

29-32: Consider documenting which migration version corresponds to FallbackSchemaVersion.

The comment says "Update this when adding new migrations" but doesn't indicate what schema state version 22 represents. Consider adding a reference to the migration file name or date for easier maintenance.


144-157: Silently falling back to manual schema may hide migration issues.

When InitSchema fails, the code logs and falls back to setupFallbackSchema(). This could mask real migration bugs in CI. Consider making the fallback opt-in or failing loudly in CI environments.

💡 Suggested approach
 func (tdb *TestDB) MustMigrate(migrationPath string) {
 	tdb.t.Helper()

 	if migrationPath == "" {
 		migrationPath = defaultMigrationPath()
 	}
 	tdb.t.Logf("using migration path: %s", migrationPath)

 	ctx := context.Background()
 	if err := database.InitSchema(ctx, tdb.DB, tdb.Dialect, migrationPath); err != nil {
-		tdb.t.Logf("Migration failed: %v - falling back to manual schema", err)
-		tdb.setupFallbackSchema()
+		if os.Getenv("CI") != "" {
+			tdb.t.Fatalf("Migration failed in CI: %v", err)
+		}
+		tdb.t.Logf("Migration failed: %v - falling back to manual schema (disable with CI=true)", err)
+		tdb.setupFallbackSchema()
 	}
 }

159-169: Fragile path resolution using runtime.Caller with hardcoded directory depth.

The triple filepath.Dir call assumes the file is always at server/pkg/testutil/testutil.go. If the file moves or the directory structure changes, this silently returns an incorrect path.

💡 Suggested improvement

Add validation that the computed path exists:

 func defaultMigrationPath() string {
 	_, currentFile, _, ok := runtime.Caller(0)
 	if !ok {
 		return "file://migrations"
 	}

 	serverDir := filepath.Dir(filepath.Dir(filepath.Dir(currentFile)))
 	migrationsDir := filepath.Join(serverDir, "migrations")

+	if _, err := os.Stat(migrationsDir); os.IsNotExist(err) {
+		// Fall back if computed path doesn't exist
+		return "file://migrations"
+	}
+
 	return "file://" + migrationsDir
 }
server/cmd/server/main.go (2)

249-250: Consider explicitly setting the log level.

The logging.Options struct accepts a Level field (per the relevant snippets), but it's not specified here. While there's likely a sensible default, consider making this configurable via cfg to allow operators to control verbosity.

💡 Suggested improvement
-	logger := logging.NewLogger(logging.Options{Format: "text", Output: os.Stdout})
+	logger := logging.NewLogger(logging.Options{
+		Level:  cfg.Logging.Level, // or a sensible default like "info"
+		Format: "text",
+		Output: os.Stdout,
+	})

410-416: Consider channel-based error handling for server startup failures.

Calling os.Exit(1) inside goroutines (lines 414, 459) bypasses deferred cleanup like db.Close(). While startup failures typically warrant immediate exit, a channel-based approach would allow graceful cleanup.

This is a minor concern since startup failures are fatal anyway, but for completeness:

errChan := make(chan error, 1)
go func() {
    if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        errChan <- err
    }
}()
// Then select on errChan and stopChan in main
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 681a8ab and e68cc9e.

📒 Files selected for processing (3)
  • CONTRIBUTING.md
  • server/cmd/server/main.go
  • server/pkg/testutil/testutil.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-15T20:03:09.042Z
Learnt from: hacktivist123
Repo: pullbase/pullbase PR: 1
File: server/pkg/config/config_test.go:89-92
Timestamp: 2026-01-15T20:03:09.042Z
Learning: Go 1.22+ fixes: range loop variables are captured per iteration, so closures (including t.Cleanup) generally don’t require shadowing or explicit capture. Since server/go.mod specifies Go 1.24.0, reviewers can assume this behavior across Go files in the repo and avoid shadowing for range variables in tests and implementation. If there’s code intended for older Go versions, consider explicit capture to preserve pre-1.22 semantics.

Applied to files:

  • server/cmd/server/main.go
  • server/pkg/testutil/testutil.go
🧬 Code graph analysis (2)
server/cmd/server/main.go (2)
server/pkg/logging/logger.go (4)
  • Warn (96-98)
  • Info (92-94)
  • NewLogger (29-47)
  • Options (23-27)
server/pkg/gitmonitor/environment_monitor.go (1)
  • EnvironmentMonitor (21-32)
server/pkg/testutil/testutil.go (2)
server/pkg/logging/logger.go (1)
  • Warn (96-98)
server/pkg/database/repository.go (1)
  • NewRepository (39-44)
🪛 LanguageTool
CONTRIBUTING.md

[style] ~68-~68: The double modal “requires built” is nonstandard (only accepted in certain dialects). Consider “to be built”.
Context: ...gs=test ./... ``` Full tests (requires built UI assets for server without stubs): `...

(NEEDS_FIXED)

🪛 markdownlint-cli2 (0.18.1)
CONTRIBUTING.md

32-32: Bare URL used

(MD034, no-bare-urls)


83-83: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: integration-tests
  • GitHub Check: Workers Builds: pullbase
🔇 Additional comments (18)
CONTRIBUTING.md (4)

31-33: LGTM!

The expanded PR workflow steps provide clearer guidance for contributors, appropriately separating testing, documentation updates, and PR submission.


42-44: LGTM!

Good addition explaining the SQLite 3.35.0+ requirement with the rationale about ALTER TABLE ... DROP COLUMN support for down migrations.


60-67: LGTM!

Clear testing guidance with the quick sanity check using stubs.


70-79: LGTM!

Good addition of full tests and integration tests guidance with clear prerequisites noted.

server/pkg/testutil/testutil.go (7)

7-18: LGTM!

Import additions are appropriate: filepath and runtime support the new defaultMigrationPath() helper, and logging replaces direct slog usage per the project's centralized logging refactor.


35-42: LGTM!

Adding Config and Dialect fields to TestDB provides useful context for tests that need to inspect connection details or dialect-specific behavior.


119-121: LGTM!

Correctly updated to use the project's centralized logging.Warn helper instead of direct slog usage.


347-354: LGTM - Schema version tracking.

Correctly inserts the fallback schema version into schema_migrations to maintain consistency with the migration system.


402-412: LGTM - Proper context cleanup.

Attaching cancel to t.Cleanup ensures context cancellation happens automatically when the test completes, preventing resource leaks. Based on learnings, Go 1.22+ handles closure captures correctly, so this pattern is safe.


366-392: Both WaitForDBConnection and PollOptions are properly defined in the same package (server/pkg/testutil/). PollOptions is defined at server/pkg/testutil/helpers.go:14 and the WaitForDBConnection method is defined at server/pkg/testutil/helpers.go:323. They are fully accessible and visible within the package scope.

Likely an incorrect or invalid review comment.


171-243: The fallback schema only supports PostgreSQL and is correct for its context.

The setupFallbackSchema() method is only invoked with PostgreSQL containers. Both SetupTestDB() and SetupTestDBWithConfig() explicitly call StartPostgresContainer() with Dialect: database.DialectPostgres hardcoded. No code path creates a TestDB with SQLite dialect. While the codebase supports multiple dialects for the main application, the testutil package's TestDB is PostgreSQL-only, making the PostgreSQL-specific syntax (SERIAL, JSONB, etc.) appropriate for its exclusive use case.

Likely an incorrect or invalid review comment.

server/cmd/server/main.go (7)

32-32: LGTM on the logging import and bootstrap secret file handling.

The transition to the internal logging package is clean, and the writeBootstrapSecretFile function has proper error handling with appropriate directory creation, file permissions (0600), and sync before close.

Also applies to: 46-79


81-161: Certificate generation implementation is well-designed.

Good choices: ECDSA P-256 curve, appropriate file permissions (0600 for key, 0644 for cert), and comprehensive SAN coverage (localhost DNS + IPv4/IPv6 loopback). The 1-year validity is reasonable for development certificates.


455-461: Good operational logging with TLS metadata.

Including cert_modtime and key_modtime in the startup log is helpful for operators to verify which certificates are loaded. This aids debugging certificate rotation issues.


333-386: Bootstrap secret lifecycle is well-implemented.

Good security practices observed:

  • Secret cleared from memory after use (line 375)
  • Leftover secret file removed when admin already exists
  • Proper error handling for file operations
  • Clear logging for each secret source (file, config, generated)

464-475: Clean shutdown sequence with appropriate timeout.

The 5-second graceful shutdown timeout is reasonable, and using Warn instead of Error for shutdown failures is appropriate since in-flight requests may have completed.


186-196: Logging migration is consistent throughout startup flow.

All logging calls follow the consistent logging.Error/Warn/Info(msg, key, value, ...) pattern. Error messages are descriptive and include relevant context (paths, errors, dialect info).

Also applies to: 199-235


314-322: CSRF protection is properly implemented through SameSite cookies and token-based authentication.

The removal of a dedicated CSRF field from the API struct is not a security concern. The codebase uses SameSite: Lax cookies for session-based authentication and Bearer token authentication for API clients. This dual approach provides robust CSRF protection: session cookies are protected via the SameSite attribute (along with HttpOnly and Secure flags), while Bearer token authentication is inherently CSRF-safe since it doesn't rely on cookies for state. No dedicated CSRF middleware is needed for this architecture.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@hacktivist123 hacktivist123 merged commit ea09c82 into main Jan 15, 2026
4 of 5 checks passed
@hacktivist123 hacktivist123 deleted the chore/bug-fixes branch January 15, 2026 21:19
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/pkg/testutil/testutil.go (1)

70-76: Wire config.StartupTimeout into the wait strategy.

The config.StartupTimeout (5 minutes) is configured but not applied to postgres.BasicWaitStrategies(). The wait strategy uses a default 60-second startup timeout instead. Pass the timeout explicitly:

Suggested fix
container, err := postgres.Run(ctx,
	config.Image,
	postgres.WithDatabase(config.Database),
	postgres.WithUsername(config.Username),
	postgres.WithPassword(config.Password),
	postgres.BasicWaitStrategies().WithStartupTimeout(config.StartupTimeout),
)
🤖 Fix all issues with AI agents
In `@server/pkg/testutil/testutil.go`:
- Around line 164-174: The file:// path construction produces backslashes on
Windows; update defaultMigrationPath to normalize the filesystem path to forward
slashes before prefixing with "file://", e.g. call filepath.ToSlash on the
migrationsDir (or its result) so the returned string uses forward slashes, and
also update migrationPathForDialect where file:// + filepath.Join(...) is used
to similarly call filepath.ToSlash on the joined path to ensure Windows paths
become "file:///C:/..." style URLs; reference the functions defaultMigrationPath
and migrationPathForDialect and normalize the path string after filepath.Join
but before adding the "file://" prefix.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 942e300 and 8afafa3.

📒 Files selected for processing (1)
  • server/pkg/testutil/testutil.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-15T20:03:09.042Z
Learnt from: hacktivist123
Repo: pullbase/pullbase PR: 1
File: server/pkg/config/config_test.go:89-92
Timestamp: 2026-01-15T20:03:09.042Z
Learning: Go 1.22+ fixes: range loop variables are captured per iteration, so closures (including t.Cleanup) generally don’t require shadowing or explicit capture. Since server/go.mod specifies Go 1.24.0, reviewers can assume this behavior across Go files in the repo and avoid shadowing for range variables in tests and implementation. If there’s code intended for older Go versions, consider explicit capture to preserve pre-1.22 semantics.

Applied to files:

  • server/pkg/testutil/testutil.go
🧬 Code graph analysis (1)
server/pkg/testutil/testutil.go (4)
server/pkg/database/database.go (1)
  • New (34-52)
server/pkg/logging/logger.go (1)
  • Warn (96-98)
server/pkg/testutil/helpers.go (1)
  • PollOptions (16-20)
server/pkg/database/repository.go (2)
  • Repository (24-27)
  • NewRepository (39-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: integration-tests
  • GitHub Check: Workers Builds: pullbase
🔇 Additional comments (9)
server/pkg/testutil/testutil.go (9)

7-16: Imports align with new migration-path and logging helpers.
Everything added here is used by the new logic.


23-29: Verify FallbackSchemaVersion stays aligned with the newest migration.
Hard-coded versions drift easily as migrations grow.


32-36: Exposing Config/Dialect on TestDB is useful.
Clean addition for downstream helpers.


120-126: Warn-level logging on termination failures is helpful.
Good visibility without failing tests.


152-156: Default migration path + logging is clear.
Nice traceability when auto-selecting the path.


191-205: Fallback schema expansion + migration tracking look solid.
The added columns, triggers, and schema_migrations handling align with the new fallback behavior.

Also applies to: 220-223, 240-242, 246-248, 296-298, 338-347, 353-359


371-375: Polling for DB readiness before migrations improves stability.
Good reliability improvement for container startup races.

Also applies to: 389-393


408-410: Cleanup-cancelled contexts prevent leaks.
Using t.Cleanup here is the right lifecycle hook.

Also applies to: 414-416


103-118: Revert this suggestion; database.New cannot return non-nil DB with error.

Both newPostgres and newSQLite explicitly call db.Close() before returning an error (lines 72 and 107, respectively). Therefore, database.New will either return a valid, open connection with err == nil, or an error with db == nil. No connection leak is possible in the retry loop.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant