Skip to content

feat(nubi): add query timeout flag, resilient polling, and recovery hints - #115

Open
blue4209211 wants to merge 2 commits into
mainfrom
feat/nubi-query-timeout-and-hints
Open

feat(nubi): add query timeout flag, resilient polling, and recovery hints#115
blue4209211 wants to merge 2 commits into
mainfrom
feat/nubi-query-timeout-and-hints

Conversation

@blue4209211

Copy link
Copy Markdown
Contributor

Summary of Changes

1. Add --timeout / -t flag to nbctl nubi query

  • Allows configuring maximum wait duration for queries (e.g. --timeout 5m, -t 2m).
  • Defaults to 0 (unlimited / wait until complete or canceled, preserving existing behavior).

2. Resilient Polling & Live Progress Updates

  • Performs an immediate check on start so quick investigations resolve without a 2-second wait and conversation metadata is captured immediately.
  • Retries up to 5 consecutive transient network/HTTP errors (e.g. transient 500s or temporary HTTP timeouts) instead of aborting the query while the server is actively working.
  • Updates spinner suffix dynamically with statusText from GetConversation.

3. Clear Post-Timeout & Post-Cancellation Guidance

When a query times out or is canceled:

  • Text Mode:
    • Displays: Query timed out after <duration>. or Request canceled.
    • Informs the user that the investigation was triggered and may still be running or completed server-side.
    • Prints the Session ID and Conversation ID (if resolved).
    • Gives the exact command to retrieve the response once completed:
      nbctl nubi get <conversation-id>
      # or: nbctl nubi get --session-id <session-id>
    • Provides the web console URL.
    • Explains how to increase the timeout or run asynchronously.
  • JSON Mode (--format json / -o json):
    • Emits structured JSON with status, error, session ID, conversation ID, and recovery hint.

4. Machine-Readable Trigger Errors & Access Hints

  • When --async -o json or synchronous -o json encounters a trigger error (such as api: user does not have access), it now outputs structured JSON on stdout instead of dumping raw Go error text to stderr.
  • Appends contextual diagnostic hints when account-level access is denied, guiding users to verify the account ID or assign an account role via nbctl auth assign-role.

5. Test Isolation Fix in pkg/testutil/helpers.go

  • Ensured RunWithMockServer sets NBCTL_TESTING=true during mock execution to prevent tests from reading ~/.nudgebee/config and connecting to production.

Verification

  • Unit tests added in cmd/nubi_test.go covering timeout in text/JSON, transient error retry, and access denied errors.
  • All unit tests passing (go test ./...).
  • Linter passing (make lint / golangci-lint run with 0 issues).

…ints

- Add --timeout / -t flag to nbctl nubi query (default: 0 / unlimited)
- Add resilient polling in nubi query to retry transient errors and update spinner
- Display Session ID, Conversation ID, nbctl nubi get command, and browser URL when query times out or is canceled
- Emit structured JSON error payload when -o json / --format json is requested on trigger failure or timeout
- Provide contextual diagnostic hints when account-level access is denied
- Fix RunWithMockServer test isolation by ensuring NBCTL_TESTING=true is set
- Add comprehensive test coverage for query timeouts, retries, and access errors

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a --timeout flag to the nubi query command, improves error handling for timeouts and cancellations (with detailed user guidance for both text and JSON outputs), and refactors the polling mechanism to tolerate transient errors and use a ticker. It also adds comprehensive tests for these scenarios. The review feedback suggests improving sub-second duration rounding to avoid displaying 0s and enhancing context error checks in the polling loop to ensure robust handling of cancellations and timeouts.

Comment thread cmd/nubi_query.go
Comment thread cmd/nubi_query.go Outdated
@blue4209211

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a '--timeout' flag to the 'nubi query' command, enhances error handling and recovery messaging for timeouts and cancellations (supporting both text and JSON outputs), and refactors the polling mechanism to use a ticker with transient error retries. The review feedback highlights several improvement opportunities, including addressing a potential data race with the spinner suffix, avoiding thread-unsafe dynamic environment variable modifications in tests, robustly distinguishing trigger errors from polling errors instead of relying on string matching, and ensuring non-zero exit codes are returned on failure in JSON mode.

Comment thread cmd/nubi_query.go
}

// If polling failed after triggering investigation, provide recovery information
if nubiClient.SessionID != "" && !strings.Contains(err.Error(), "triggering investigation") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Relying on strings.Contains(err.Error(), "triggering investigation") to distinguish between trigger failures and polling failures is fragile. If the error wrapping message in triggerAndPoll is ever modified or translated, this check will silently break, potentially leading to misleading recovery hints being displayed. Consider defining a custom error type (e.g., TriggerError) or using a structured error check to make this distinction robust.

Comment thread cmd/nubi_query.go
Comment on lines +365 to +367
if s.spinner != nil && statusText != "" {
s.spinner.Suffix = " " + statusText
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Updating s.spinner.Suffix directly while the spinner is active (running in its own goroutine) introduces a data race, as briandowns/spinner does not synchronize access to its public fields. This can trigger the Go race detector (go test -race). Consider checking if the spinner is active before updating, or using a thread-safe wrapper/alternative if race-free execution is required.

Comment thread pkg/testutil/helpers.go
Comment on lines +88 to +89
_ = os.Setenv("NBCTL_TESTING", "true")
defer func() { _ = os.Unsetenv("NBCTL_TESTING") }()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Calling os.Setenv and os.Unsetenv dynamically inside test helper functions is not thread-safe and can cause concurrent map write panics if tests are run in parallel or with the -race flag.

Since pkg/testutil is a test-only package, you can safely set NBCTL_TESTING once in an init() function at the package level and remove these dynamic set/unset calls entirely.

For example, add this to pkg/testutil/helpers.go:

func init() {
	os.Setenv("NBCTL_TESTING", "true")
}

And then remove the os.Setenv and os.Unsetenv calls from RunWithMockServer and RunWithSimpleGraphQL.

Comment thread cmd/nubi_query.go
jsonResp["hint"] = hint
}
format.GetFormat().Print(jsonResp)
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Returning nil (which exits with status 0) when a query execution or trigger fails in JSON mode makes it difficult for scripts, CI/CD pipelines, or automation tools to detect failures using standard exit codes. Consider returning a non-zero exit code on failure even when outputting JSON, perhaps by returning a custom error that is silenced by Cobra (using SilenceErrors: true) so it doesn't print raw error text to stderr.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant