Skip to content

feat: exponential backoff, CLI UX, service management, and doc restructure#30

Open
unclesp1d3r wants to merge 48 commits intomainfrom
1-implement-exponential-backoff-with-jitter-for-api-requests
Open

feat: exponential backoff, CLI UX, service management, and doc restructure#30
unclesp1d3r wants to merge 48 commits intomainfrom
1-implement-exponential-backoff-with-jitter-for-api-requests

Conversation

@unclesp1d3r
Copy link
Copy Markdown
Member

@unclesp1d3r unclesp1d3r commented Mar 27, 2026

Summary

Implements exponential backoff with jitter for all API requests, adds CLI UX improvements, cross-platform service management, and restructures project documentation.

34 files changed (+1,636 / -351) across 4 main areas:

  1. API retry logicbackon crate with exponential backoff, jitter, and precise error classification
  2. CLI improvements — verbose flag, exit codes, NO_COLOR, progress bars, system-level default paths
  3. Service managementservice-manager crate for systemd/launchd/sc.exe install/start/stop
  4. Documentation — AGENTS.md split into ARCHITECTURE.md, docs/development.md, docs/testing.md

What Changed

API Retry / Backoff

  • RetryConfig maps AgentConfig backoff fields to backon::ExponentialBuilder
  • is_retryable() classifies errors: only 5xx and transient network errors (timeout/connect) retry; auth, parse, not-found fail immediately
  • All ApiClient methods use with_retry internally — retries are transparent to callers
  • ApiClient::new and url() now return Result instead of panicking
  • Error reports to server include full anyhow chain via {:#} format

CLI UX

  • -v/--verbose flag (count-based: -v = debug, -vv = trace)
  • Differentiated exit codes: 1 = runtime, 2 = config, 3 = auth (via error downcasting)
  • NO_COLOR env var and TTY detection for ANSI output
  • indicatif progress bars for file downloads (bytes/speed/ETA)
  • --json-logs now supports HASH_HIVE_JSON_LOGS env var
  • long_about with lifecycle description, value_name hints on all args
  • Warning when using default localhost server URL
  • Actionable troubleshooting hints in config error messages

Service Management

  • service-install, service-uninstall, service-start, service-stop, service-status subcommands
  • Cross-platform: systemd (Linux), launchd (macOS), sc.exe (Windows)
  • Passes through HASH_HIVE_* env vars, sets restart-on-failure with 5s delay
  • Windows relies on existing Ctrl+C handler — no SCM integration needed

System Paths

  • Default data dir: /var/lib/hash_hive_agent (Unix), %ProgramData%\HashHive\Agent\data (Windows)
  • Default cache dir: /var/cache/hash_hive_agent (Unix), %ProgramData%\HashHive\Agent\cache (Windows)

Error Handling Improvements

  • Streaming checksum verification (was fs::read — OOM on 100GB+ files)
  • Channel send failures in hashcat session readers now break the loop (was silently discarded)
  • extract_error_message logs body-read failures instead of returning empty string
  • parse_status_progress logs missing JSON fields instead of silently defaulting to zero
  • Service uninstall surfaces unexpected stop errors instead of silently dropping

Documentation

  • AGENTS.md → thin routing file pointing to real docs
  • New: ARCHITECTURE.md (system overview, retry design, concurrency)
  • New: docs/development.md (toolchain, just commands, CI)
  • New: docs/testing.md (test org, libraries, coverage)
  • CONTRIBUTING.md enhanced with lint policy, error handling, nav links
  • GOTCHAS.md updated with current retry testing approach

Dependency Updates

  • MSRV bumped to 1.94 (current stable)
  • sysinfo 0.35 → 0.38, time 0.3.45 → 0.3.47 (fixes RUSTSEC-2026-0009)
  • New deps: backon, indicatif, service-manager
  • Collapsed nested ifs into let-chains (Rust 1.87+ feature)

Test plan

  • 25 unit + integration tests pass (just test)
  • Clippy clean (cargo clippy --all-targets)
  • cargo audit clean (no advisories)
  • All pre-commit hooks pass
  • CI green on push
  • Manual: verify hash-hive-agent --help shows new long_about and verbose flag
  • Manual: verify hash-hive-agent service-install on Linux (requires root)
  • Manual: verify progress bar renders during file download
  • Manual: verify NO_COLOR=1 suppresses ANSI codes
  • Manual: verify exit code 2 on missing token, exit code 3 on bad auth

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…m shellcheck and mdformat hooks

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…stallation, migration, plugins, and version management

- Created `tasks-patterns.md` detailing common task patterns including pre/post hooks, conditional execution, environment-specific tasks, multi-stage builds, and CI/CD patterns.
- Added `tools-installation.md` covering basic tool installation, configuration in `mise.toml`, tool-specific settings, and best practices for managing tool versions.
- Introduced `tools-migration.md` to guide users migrating from other version managers like asdf, nvm, rbenv, and pyenv to Mise.
- Developed `tools-plugins.md` explaining the plugin system, core vs plugin tools, common plugins, and plugin management strategies.
- Documented the `.tool-versions` file format in `tools-versions.md`, including compatibility with asdf and best practices for version management.
- Added `tile.json` for the pantheon-ai/mise-complete tile, summarizing its purpose and skills.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…nt strategic compact and TDD workflow skills

- Introduced `security-review` skill with comprehensive security checklist for authentication, input validation, SQL injection prevention, and more.
- Added `cloud-infrastructure-security` skill to ensure best practices in cloud deployments, IAM policies, and CI/CD pipelines.
- Created `strategic-compact` skill to suggest manual context compaction at logical intervals, enhancing workflow efficiency.
- Implemented `tdd-workflow` skill enforcing test-driven development principles with detailed testing patterns and best practices.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
- Introduced guidelines for keeping ORM usage in the infrastructure layer to isolate persistence complexity.
- Added principles for maintaining web framework concerns in the interface layer to enable delivery mechanism substitution.
- Established the importance of verifying architectural boundaries with automated tests to prevent dependency violations.
- Emphasized the need for testing each architectural layer in isolation for fast feedback and precise failure location.
- Highlighted the significance of designing for testability from the start to facilitate comprehensive testing.
- Asserted that tests are part of the system architecture, ensuring they follow dependency rules and are maintainable.
- Mandated explicit declaration of all dependencies in use case constructors to enhance testing and prevent hidden coupling.
- Defined input and output ports for use cases to decouple them from delivery mechanisms.
- Stressed that use cases must not contain presentation logic, allowing for multiple UI implementations.
- Clarified that use cases should orchestrate entities rather than implement business rules to avoid duplication.
- Ensured each use case has a single responsibility to isolate change impact and enable parallel development.
- Specified that the use case defines the transaction boundary to ensure data consistency and prevent partial updates.
- Updated tile.json to reflect the new version and summary of the software design principles.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
… software design principles

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…uests

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…with jitter for API requests

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
- Add 4 wiremock integration tests for retry behavior (5xx retry,
  4xx no-retry, retry exhaustion, 404 no-retry)
- Replace expect() calls with proper error handling: ApiClient::new
  returns Result, url() returns Result with new UrlParse variant
- Fix ~70 clippy warnings across the codebase: add #[non_exhaustive]
  to all public enums, remove unused async, fix shadow variables,
  replace as-casts with safe conversions, add const to pure functions
- Update AGENTS.md and GOTCHAS.md to reflect UrlParse variant and
  test lint suppression patterns

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
The previous test sent a real HTTP request to a non-routable address
(192.0.2.1) and silently passed if the error type didn't match. Now
uses wiremock with a 5s delayed response and a 10ms client timeout
to deterministically trigger a timeout error.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
@unclesp1d3r unclesp1d3r linked an issue Mar 27, 2026 that may be closed by this pull request
4 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds docs and tooling updates, expands ignore/pre-commit rules, implements configurable exponential backoff+jitter and retry classification in the API client (with tests), integrates retry config and async error reporting into the agent, and adds service management and robustness improvements across task/hashcat/downloader code.

Changes

Cohort / File(s) Summary
Pre-commit & Ignore
\.pre-commit-config.yaml, \.gitignore
Adjusted pre-commit hook exclusion patterns; expanded .gitignore to ignore TESSL, local patterns, and various AI/agent/editor artifacts.
Documentation & Guides
ARCHITECTURE.md, AGENTS.md, CONTRIBUTING.md, GOTCHAS.md, CLAUDE.md, docs/development.md, docs/testing.md
Added ARCHITECTURE and development/testing docs; replaced AGENTS.md with a compact index; tightened contributing/gotchas guidance and removed one CLAUDE.md reference.
API client & tests
src/api/client.rs, tests/api_retry_test.rs
Added RetryConfig (From), made ApiClient::new accept retry config and return Result, added is_retryable, with_retry using backon (exponential backoff + jitter), wrapped public API methods with retry, changed URL helper to return Result, improved error extraction, and added Wiremock tests.
Agent integration
src/agent/mod.rs
Derives retry config for ApiClient, initializes client with context, logs task IDs, sends AgentErrorReport on task failures and warns if reporting fails; minor loop/logging adjustments.
CLI, service & main
src/cli.rs, src/service.rs, src/lib.rs, src/main.rs
Expanded CLI (verbose flag, value_name metadata, subcommands); added public service module and src/service.rs implementing service install/start/stop/status; reworked main flow, exit codes, log-level resolution, and error classification.
Task runner & hashcat session
src/task/runner.rs, src/hashcat/session.rs
Made status/progress parsing non-optional with defensive defaults and debug logs, refined exit-code→TaskStatus mapping, renamed session internals (session_namename), added #[non_exhaustive] to SessionEvent, and made stream readers stop when sends fail.
Downloader & checksum
src/task/downloader.rs
Switched filename derivation to next_back(), added download progress UI (spinner/progress bar), streamed checksum verification with buffered reads, and updated per-chunk handling and progress finalization.
Config & defaults
src/config/..., src/config/defaults.rs
Platform-aware default data/cache dirs (non-Windows absolute paths; Windows helper windows_program_data_subdir), const-ified several default providers, and adjusted AgentConfig::load to use platform helpers.
Misc. deps & tooling
Cargo.toml, .cargo/audit.toml
Bumped rust-version, added backon, indicatif, service-manager, updated sysinfo, and added a cargo-audit config file.
Tests & CI
tests/api_retry_test.rs
Added integration tests covering retry behavior (5xx retry, 4xx non-retry, exhausted retries, zero-retries) and a test helper for fast backoffs.

Sequence Diagram(s)

sequenceDiagram
  participant Agent as Agent (run)
  participant Config as AgentConfig
  participant Api as ApiClient (with_retry)
  participant Server as HashHive Server

  Agent->>Config: read retry params
  Agent->>Api: ApiClient::new(base_url, retry_config)
  Agent->>Api: create_session / poll / report
  Api->>Server: HTTP request
  alt transient failure (5xx / timeout / connect)
    Server--x Api: 5xx / timeout
    Api->>Api: backoff + jitter (wait)
    Api->>Server: retry attempt (warn logged)
    note right of Api: repeats until success or max_retries
  else non-retryable (4xx / auth / not-found)
    Server-->>Api: 4xx
    Api-->>Agent: return error (no retry)
  end
  Server-->>Api: success
  Api-->>Agent: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code and docs anew,
I taught our client jittered queue,
When servers grumble, I wait and try—
I log each hop and nibble pie. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changeset fully implements all requirements from issue #1: exponential backoff with jitter, configurable retry parameters from AgentConfig, selective retry logic for 5xx/transient errors only, structured logging, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed While the PR includes many changes beyond issue #1 (documentation updates, service management, clippy fixes, downloader progress bars), these appear to be supporting improvements that enhance code quality and maintainability without contradicting the core objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title accurately summarizes the four main areas of change: exponential backoff (API retry logic), CLI UX improvements, service management, and documentation restructuring.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, providing detailed summaries of API retry logic, CLI UX improvements, service management, system paths, error handling, documentation changes, and dependency updates.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 1-implement-exponential-backoff-with-jitter-for-api-requests

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

@unclesp1d3r unclesp1d3r self-assigned this Mar 27, 2026
…in review settings

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
- Revert sysinfo from 0.38.4 to 0.35 to match rust-version = "1.85"
  (0.38+ requires Rust 1.88+, per GOTCHAS.md)
- Remove Default impl for RetryConfig to enforce all runtime config
  flowing through AgentConfig (per AGENTS.md convention)

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Third-party plugin shell scripts in .claude/skills/ should not be
linted by shellcheck, same as .tessl/ is excluded.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
@coderabbitai coderabbitai bot added the area:api API client and server communication label Mar 27, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

Copy link
Copy Markdown

@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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.pre-commit-config.yaml:
- Line 74: Update the mdformat exclusion pattern by replacing the incorrect
pattern "exclude: ^\.tessl/" with "exclude: ^\.claude/" (or add an additional
entry if you need to exclude both) so that the .claude/ directory is properly
ignored by mdformat; locate the line containing the literal exclude: ^\.tessl/
in the .pre-commit-config.yaml and change it to exclude: ^\.claude/ (or add a
second exclude pattern) to fix the typo.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d3cd5e82-cadd-4fd5-80e9-88a5cde7c1c6

📥 Commits

Reviewing files that changed from the base of the PR and between 04d0eea and a03db15.

⛔ Files ignored due to path filters (270)
  • .claude/rules/agents.md is excluded by none and included by none
  • .claude/rules/coding-style.md is excluded by none and included by none
  • .claude/rules/development-workflow.md is excluded by none and included by none
  • .claude/rules/git-workflow.md is excluded by none and included by none
  • .claude/rules/hooks.md is excluded by none and included by none
  • .claude/rules/patterns.md is excluded by none and included by none
  • .claude/rules/performance.md is excluded by none and included by none
  • .claude/rules/rust-coding-style.md is excluded by none and included by none
  • .claude/rules/rust-hooks.md is excluded by none and included by none
  • .claude/rules/rust-patterns.md is excluded by none and included by none
  • .claude/rules/rust-security.md is excluded by none and included by none
  • .claude/rules/rust-testing.md is excluded by none and included by none
  • .claude/rules/security.md is excluded by none and included by none
  • .claude/rules/testing.md is excluded by none and included by none
  • .claude/settings.json is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/SKILL.md is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/agents/observer-loop.sh is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/agents/observer.md is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/agents/session-guardian.sh is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/agents/start-observer.sh is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/config.json is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/hooks/observe.sh is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/scripts/detect-project.sh is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/scripts/instinct-cli.py is excluded by none and included by none
  • .claude/skills/continuous-learning-v2/scripts/test_parse_instinct.py is excluded by none and included by none
  • .claude/skills/iterative-retrieval/SKILL.md is excluded by none and included by none
  • .claude/skills/security-review/SKILL.md is excluded by none and included by none
  • .claude/skills/security-review/cloud-infrastructure-security.md is excluded by none and included by none
  • .claude/skills/strategic-compact/SKILL.md is excluded by none and included by none
  • .claude/skills/strategic-compact/suggest-compact.sh is excluded by none and included by none
  • .claude/skills/tdd-workflow/SKILL.md is excluded by none and included by none
  • .codex/config.toml is excluded by none and included by none
  • .cursor/mcp.json is excluded by none and included by none
  • .gemini/settings.json is excluded by none and included by none
  • .gitignore is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/README.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/coding-guidelines/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/coding-guidelines/clippy-lints/_index.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/coding-guidelines/index/rules-index.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/domain-cli/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/domain-web/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m01-ownership/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m01-ownership/comparison.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m01-ownership/examples/best-practices.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m01-ownership/patterns/common-errors.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m01-ownership/patterns/lifetime-patterns.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m02-resource/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m03-mutability/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m04-zero-cost/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m05-type-driven/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m06-error-handling/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m06-error-handling/examples/library-vs-app.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m06-error-handling/patterns/error-patterns.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m07-concurrency/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m07-concurrency/comparison.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m07-concurrency/examples/thread-patterns.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m07-concurrency/patterns/async-patterns.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m07-concurrency/patterns/common-errors.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m09-domain/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m10-performance/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m10-performance/patterns/optimization-guide.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m11-ecosystem/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m12-lifecycle/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m13-domain-error/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m14-mental-model/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m14-mental-model/patterns/thinking-in-rust.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m15-anti-pattern/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/m15-anti-pattern/patterns/common-mistakes.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/meta-cognition-parallel/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-call-graph/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-code-navigator/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-deps-visualizer/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-learner/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-refactor-helper/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-skill-creator/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-symbol-analyzer/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/rust-trait-explorer/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/SKILL.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/checklists/before-unsafe.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/checklists/common-pitfalls.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/checklists/review-unsafe.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/examples/ffi-patterns.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/examples/safe-abstraction.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/metadata.json is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/_sections.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/_template.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-01-no-string-direct.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-02-read-ffi-docs.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-03-drop-for-c-ptr.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-04-panic-boundary.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-05-portable-types.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-06-string-abi.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-07-no-drop-external.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-08-error-handling.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-09-ref-not-ptr.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-10-thread-safety.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-11-packed-ub.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-12-invariant-doc.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-13-data-layout.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-14-stable-layout.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-15-validate-external.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-16-closure-to-c.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-17-opaque-types.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ffi-18-no-trait-objects.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/general-01-no-abuse.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/general-02-not-for-perf.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/general-03-no-alias.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/io-01-raw-handle.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/mem-01-repr-layout.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/mem-02-no-other-process.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/mem-03-no-auto-drop-foreign.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/mem-04-reentrant.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/mem-05-bitfield-crates.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/mem-06-maybeuninit.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ptr-01-no-thread-share.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ptr-02-prefer-nonnull.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ptr-03-phantomdata.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ptr-04-alignment.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ptr-05-no-const-to-mut.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/ptr-06-prefer-cast.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-01-panic-safety.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-02-verify-invariants.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-03-no-uninit-api.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-04-double-free.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-05-send-sync.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-06-no-raw-ptr-api.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-07-unsafe-pair.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-08-no-mut-from-immut.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-09-safety-comment.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-10-safety-doc.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/safety-11-assert-not-debug.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/union-01-avoid-except-ffi.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/skills/unsafe-checker/rules/union-02-no-cross-lifetime.md is excluded by none and included by none
  • .tessl/tiles/actionbook/rust-skills/tile.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/SKILL.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/instructions.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-0/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-0/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-0/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-1/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-1/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-1/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-2/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-2/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-2/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-3/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-3/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-3/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-4/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-4/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/scenario-4/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/summary.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/evals/summary_infeasible.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/references/language_specific_guides.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/references/modern_docker_features.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/references/multistage_builds.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/references/optimization_patterns.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/references/security_best_practices.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/scripts/generate_dockerignore.sh is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/scripts/generate_golang.sh is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/scripts/generate_java.sh is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/scripts/generate_nodejs.sh is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/generator/scripts/generate_python.sh is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/tile.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/SKILL.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/instructions.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-1/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-1/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-1/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-2/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-2/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-2/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-3/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-3/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-3/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-4/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-4/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-4/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-5/capability.txt is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-5/criteria.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/scenario-5/task.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/summary.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/evals/summary_infeasible.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/references/docker_best_practices.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/references/optimization_guide.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/references/security_checklist.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/dockerfile-toolkit/validator/scripts/dockerfile-validate.sh is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/SKILL.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/assets/examples/README.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/assets/templates/action/docker/entrypoint.sh is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/assets/templates/action/javascript/index.js is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/references/advanced-triggers.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/references/best-practices.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/references/common-actions.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/references/custom-actions.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/references/expressions-and-contexts.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/references/modern-features.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/github-actions-generator/tile.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/SKILL.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/config-anti-patterns.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/config-best-practices.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/config-management.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/config-structure.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/env-definition.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/env-hierarchies.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/env-loading.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/env-patterns.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tasks-definition.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tasks-execution.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tasks-organization.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tasks-patterns.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tools-installation.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tools-migration.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tools-plugins.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/references/tools-versions.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/mise-complete/tile.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/moscow-prioritization/SKILL.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/moscow-prioritization/references/categorization-decision-tree.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/moscow-prioritization/references/effort-balancing-and-tradeoffs.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/moscow-prioritization/references/facilitator-workshop-template.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/moscow-prioritization/tile.json is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/SKILL-FULL.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/SKILL.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/adapt-anti-corruption-layer.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/adapt-controller-thin.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/adapt-gateway-abstraction.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/adapt-mapper-translation.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/adapt-presenter-formats.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/anti-patterns-and-frameworks.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/bound-boundary-cost-awareness.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/bound-defer-decisions.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/bound-humble-object.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/bound-main-component.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/bound-partial-boundaries.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/bound-service-internal-architecture.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/comp-common-closure.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/comp-common-reuse.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/comp-reuse-release-equivalence.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/comp-screaming-architecture.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/comp-stable-dependencies.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/dep-acyclic-dependencies.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/dep-data-crossing-boundaries.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/dep-interface-ownership.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/dep-inward-only.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/dep-no-framework-imports.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/dep-stable-abstractions.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/detailed-examples.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/entity-encapsulate-invariants.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/entity-no-persistence-awareness.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/entity-pure-business-rules.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/entity-rich-not-anemic.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/entity-value-objects.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/frame-di-container-edge.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/frame-domain-purity.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/frame-logging-abstraction.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/frame-orm-in-infrastructure.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/frame-web-in-infrastructure.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/test-boundary-verification.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/test-layer-isolation.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/test-testable-design.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/test-tests-are-architecture.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/usecase-explicit-dependencies.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/usecase-input-output-ports.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/usecase-no-presentation-logic.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/usecase-orchestrates-not-implements.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/usecase-single-responsibility.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/references/usecase-transaction-boundary.md is excluded by none and included by none
  • .tessl/tiles/pantheon-ai/software-design-principles/tile.json is excluded by none and included by none
  • Cargo.lock is excluded by !**/*.lock and included by none
  • tests/api_retry_test.rs is excluded by none and included by none
📒 Files selected for processing (24)
  • .coderabbit.yaml
  • .mcp.json
  • .pre-commit-config.yaml
  • AGENTS.md
  • Cargo.toml
  • GOTCHAS.md
  • src/agent/heartbeat.rs
  • src/agent/mod.rs
  • src/agent/polling.rs
  • src/agent/shutdown.rs
  • src/api/client.rs
  • src/api/error.rs
  • src/api/mod.rs
  • src/api/types.rs
  • src/benchmark/mod.rs
  • src/cli.rs
  • src/config/mod.rs
  • src/hashcat/error_parser.rs
  • src/hashcat/exit_code.rs
  • src/hashcat/session.rs
  • src/platform/mod.rs
  • src/task/downloader.rs
  • src/task/runner.rs
  • tessl.json

- mdformat-toc
types: [markdown]
exclude: ^\.claude/
exclude: ^\.tessl/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find markdown files under .claude/ that are now subject to mdformat

fd -e md . .claude/ 2>/dev/null || echo "No .claude/ directory found or no markdown files"

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 808


🏁 Script executed:

cat -n .pre-commit-config.yaml | sed -n '60,80p'

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 839


🏁 Script executed:

head -50 ".claude/skills/continuous-learning-v2/SKILL.md"

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 2819


🏁 Script executed:

head -50 ".claude/skills/tdd-workflow/SKILL.md"

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 1159


Correct .tessl/ to .claude/ in mdformat exclusion pattern.

The exclude pattern references ^\.tessl/, which appears to be a typo. This directory doesn't exist in the repository, while .claude/ contains 21 markdown files (rules and skills with YAML front matter) that will now be processed by mdformat instead of being excluded.

The good news: mdformat-front-matter is configured (line 67) and properly handles YAML front matter in skill definitions, so formatting should be safe. However, the configuration error should be corrected:

Proposed fix
-            exclude: ^\.tessl/
+            exclude: ^\.claude/

Alternatively, if both directories need exclusion:

-            exclude: ^\.tessl/
+            exclude: ^(\.tessl/|\.claude/)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exclude: ^\.tessl/
exclude: ^\.claude/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.pre-commit-config.yaml at line 74, Update the mdformat exclusion pattern by
replacing the incorrect pattern "exclude: ^\.tessl/" with "exclude: ^\.claude/"
(or add an additional entry if you need to exclude both) so that the .claude/
directory is properly ignored by mdformat; locate the line containing the
literal exclude: ^\.tessl/ in the .pre-commit-config.yaml and change it to
exclude: ^\.claude/ (or add a second exclude pattern) to fix the typo.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
- Log warning on task progress report failure instead of silently
  discarding with .ok() (src/task/runner.rs)
- Report task execution failures to server via report_error API
  so operators see failures on the dashboard (src/agent/mod.rs)
- Add url_parse_error_is_not_retryable unit test to cover the
  only untested ApiError variant in is_retryable (src/api/client.rs)

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Untrack AI/LLM agent configuration files (.claude/, .cursor/mcp.json,
CLAUDE.md) that are local workflow artifacts, not project source.
AGENTS.md remains tracked as contributor-facing documentation.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Untrack .codex/, .gemini/, .coderabbit.yaml, .mcp.json, tessl.json,
and .tessl/ — local workflow files that should not be in the public
repo. Extends .gitignore to cover these alongside the Claude/Cursor
configs already ignored in 00629a0.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…d testing guides

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Copy link
Copy Markdown

@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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/task/runner.rs (1)

135-152: ⚠️ Potential issue | 🟠 Major

Don’t silence this arithmetic warning; validate the work range instead.

range.end - range.start is derived from task input. If the server ever sends end < start, the generated --limit becomes invalid or huge instead of failing fast.

🛡️ Safer approach
-    let args = build_hashcat_args(&task, &task_dir);
+    let args = build_hashcat_args(&task, &task_dir)?;
@@
-#[allow(clippy::arithmetic_side_effects)]
-fn build_hashcat_args(task: &TaskDescriptor, task_dir: &std::path::Path) -> Vec<String> {
+fn build_hashcat_args(task: &TaskDescriptor, task_dir: &std::path::Path) -> Result<Vec<String>> {
     let mut args = vec![
         "-m".to_owned(),
         task.hash_type_id.to_string(),
@@
     if let Some(ref range) = task.work_range {
+        let limit = range
+            .end
+            .checked_sub(range.start)
+            .context("invalid work range: end is before start")?;
         args.push("--skip".to_owned());
         args.push(range.start.to_string());
         args.push("--limit".to_owned());
-        args.push((range.end - range.start).to_string());
+        args.push(limit.to_string());
     }
@@
-    args
+    Ok(args)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/task/runner.rs` around lines 135 - 152, The arithmetic warning comes from
unvalidated subtraction of work_range.end - work_range.start in
build_hashcat_args; instead validate the range before computing the limit:
change build_hashcat_args to return a Result<Vec<String>, Error> (or a custom
TaskValidationError), check that if let Some(ref range) = task.work_range then
ensure range.end >= range.start, and only then push "--skip" and "--limit" with
(range.end - range.start).to_string(); if the check fails return an Err
describing the invalid range (or otherwise surface the error) so callers of
build_hashcat_args (and their callers) can fail fast rather than generating an
invalid huge unsigned value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@GOTCHAS.md`:
- Around line 49-52: Update the "Retry Classification Testing" gotcha to remove
the flaky 192.0.2.1 guidance and instead instruct using the deterministic
delayed wiremock response approach used in src/api/client.rs (the test that
simulates a delayed response) when exercising retry behavior; state that only
ApiError::Request responses where is_timeout() or is_connect() are considered
retryable (is_request() may include permanent builder/decode errors), and
recommend running such tests inside a tokio runtime built with
tokio::runtime::Builder::new_current_thread() to deterministically reproduce
timeout/connect retries.

In `@src/agent/mod.rs`:
- Around line 69-79: The current error branch after task::execute only sends an
AgentErrorReport, which can leave tasks stuck in Running if execute failed after
reporting Running but before sending a terminal report_status; update
task::execute to return a typed outcome (e.g., enum ExecuteOutcome {
Terminal(Status), NonTerminalError(Error) }) or otherwise ensure it performs a
best-effort terminal transition itself for setup/download failures in
src/task/runner.rs, and change the agent's handler to inspect the returned
outcome rather than treating all errors the same so it will call
client.report_status(...Failed...) when needed; reference task::execute,
AgentErrorReport, report_status, and client.report_error when making the change.

In `@src/api/client.rs`:
- Around line 124-130: Generate a unique idempotency key once at the start of
get_next_task (e.g., a UUID) and reuse that same key for every retry attempt so
duplicate POSTs are deduplicated; move key generation outside the with_retry
closure and add it to the request headers by setting "Idempotency-Key" on the
request returned from authed_post (before send()), keeping the rest of the flow
(send().await and handle_response) unchanged so get_next_task, with_retry,
authed_post, send, and handle_response continue to be used as before.
- Around line 27-35: The Api client never applies the configured request timeout
— update RetryConfig to include request_timeout (Duration), extend the impl
From<&crate::config::AgentConfig> for RetryConfig to copy cfg.request_timeout
into RetryConfig, and then pass that timeout into the reqwest client builder
inside ApiClient::new by calling .timeout(retry_config.request_timeout) on
Client::builder(); this ensures hanging requests honor the configured timeout so
is_timeout()-based retry logic can run.

---

Outside diff comments:
In `@src/task/runner.rs`:
- Around line 135-152: The arithmetic warning comes from unvalidated subtraction
of work_range.end - work_range.start in build_hashcat_args; instead validate the
range before computing the limit: change build_hashcat_args to return a
Result<Vec<String>, Error> (or a custom TaskValidationError), check that if let
Some(ref range) = task.work_range then ensure range.end >= range.start, and only
then push "--skip" and "--limit" with (range.end - range.start).to_string(); if
the check fails return an Err describing the invalid range (or otherwise surface
the error) so callers of build_hashcat_args (and their callers) can fail fast
rather than generating an invalid huge unsigned value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf339d6b-2556-4ffe-98a3-0707f1ce0aeb

📥 Commits

Reviewing files that changed from the base of the PR and between a03db15 and 33c4eb7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .gitignore
  • AGENTS.md
  • ARCHITECTURE.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • GOTCHAS.md
  • docs/development.md
  • docs/testing.md
  • src/agent/mod.rs
  • src/api/client.rs
  • src/task/runner.rs
💤 Files with no reviewable changes (1)
  • CLAUDE.md
✅ Files skipped from review due to trivial changes (4)
  • .gitignore
  • docs/testing.md
  • CONTRIBUTING.md
  • ARCHITECTURE.md

Comment on lines +49 to +52
## Retry Classification Testing

- `reqwest::Error` has no public constructors -- test `is_retryable` with real network calls (e.g., `192.0.2.1` for timeout/connect errors) inside a `tokio::runtime::Builder::new_current_thread()` block.
- Only `is_timeout()` and `is_connect()` are retryable for `ApiError::Request`; `is_request()` includes permanent builder/decode errors.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update this gotcha to match the new deterministic retry-test strategy.

src/api/client.rs now uses a delayed wiremock response at Lines 343-371. Keeping the 192.0.2.1 guidance here will send the next retryability test back to a flaky, environment-dependent pattern.

📝 Suggested doc update
-- `reqwest::Error` has no public constructors -- test `is_retryable` with real network calls (e.g., `192.0.2.1` for timeout/connect errors) inside a `tokio::runtime::Builder::new_current_thread()` block.
+- `reqwest::Error` has no public constructors -- prefer deterministic local fixtures for retryability tests: delayed wiremock responses for timeouts, and other local-only setups for connect failures.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Retry Classification Testing
- `reqwest::Error` has no public constructors -- test `is_retryable` with real network calls (e.g., `192.0.2.1` for timeout/connect errors) inside a `tokio::runtime::Builder::new_current_thread()` block.
- Only `is_timeout()` and `is_connect()` are retryable for `ApiError::Request`; `is_request()` includes permanent builder/decode errors.
## Retry Classification Testing
- `reqwest::Error` has no public constructors -- prefer deterministic local fixtures for retryability tests: delayed wiremock responses for timeouts, and other local-only setups for connect failures.
- Only `is_timeout()` and `is_connect()` are retryable for `ApiError::Request`; `is_request()` includes permanent builder/decode errors.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GOTCHAS.md` around lines 49 - 52, Update the "Retry Classification Testing"
gotcha to remove the flaky 192.0.2.1 guidance and instead instruct using the
deterministic delayed wiremock response approach used in src/api/client.rs (the
test that simulates a delayed response) when exercising retry behavior; state
that only ApiError::Request responses where is_timeout() or is_connect() are
considered retryable (is_request() may include permanent builder/decode errors),
and recommend running such tests inside a tokio runtime built with
tokio::runtime::Builder::new_current_thread() to deterministically reproduce
timeout/connect retries.

Comment on lines 69 to +79
if let Err(e) = task::execute(&client, &config, descriptor).await {
error!(error = %e, "task execution failed");
error!(error = %e, task_id, "task execution failed");
let report = AgentErrorReport {
severity: ErrorSeverity::Error,
message: e.to_string(),
context: None,
task_id: Some(task_id),
};
if let Err(report_err) = client.report_error(&report).await {
warn!(error = %report_err, task_id, "failed to report task error to server");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reporting the error here still leaves some tasks without a terminal state.

task::execute can fail after it has already reported Running but before any terminal report_status is sent, e.g. setup/download failures in src/task/runner.rs Lines 19-59. This branch only emits AgentErrorReport, so the server can keep the task stuck in Running. Please either make task::execute own the best-effort Failed transition for those paths, or return a typed outcome so the agent knows whether it still needs to close the task.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent/mod.rs` around lines 69 - 79, The current error branch after
task::execute only sends an AgentErrorReport, which can leave tasks stuck in
Running if execute failed after reporting Running but before sending a terminal
report_status; update task::execute to return a typed outcome (e.g., enum
ExecuteOutcome { Terminal(Status), NonTerminalError(Error) }) or otherwise
ensure it performs a best-effort terminal transition itself for setup/download
failures in src/task/runner.rs, and change the agent's handler to inspect the
returned outcome rather than treating all errors the same so it will call
client.report_status(...Failed...) when needed; reference task::execute,
AgentErrorReport, report_status, and client.report_error when making the change.

Comment on lines +27 to +35
impl From<&crate::config::AgentConfig> for RetryConfig {
fn from(cfg: &crate::config::AgentConfig) -> Self {
Self {
backoff_base: cfg.backoff_base,
backoff_max: cfg.backoff_max,
max_retries: cfg.max_retries,
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Expected: AgentConfig defines an HTTP timeout, while ApiClient::new builds a reqwest client
# without any `.timeout(...)` call.

rg -n -C2 'HTTP request timeout|pub .*timeout' src/config/mod.rs
rg -n -C2 'ApiClient::new|Client::builder\(|\.timeout\(' src/api/client.rs src/agent/mod.rs

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 1501


🏁 Script executed:

# Get context around ApiClient::new (lines 75-85 and surrounding)
sed -n '70,90p' src/api/client.rs

# Check if request_timeout is used anywhere else
rg -n 'request_timeout' src/

# Get context around line 357-359
sed -n '350,365p' src/api/client.rs

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 1596


🏁 Script executed:

# Check RetryConfig definition and struct
sed -n '1,40p' src/api/client.rs

# Search for TODO or FIXME related to timeout
rg -n 'TODO|FIXME|timeout' src/api/client.rs | head -20

# Check if request_timeout was in an earlier version or if there's documentation about it
rg -n 'struct.*Retry|impl.*RetryConfig' -A 10 src/api/client.rs

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 2551


The configured HTTP timeout is not applied to reqwest::Client.

src/config/mod.rs defines request_timeout: Duration, but ApiClient::new only accepts base_url and retry_config parameters. The client builder at lines 76-78 never calls .timeout(), leaving production requests on reqwest's default (no timeout) behavior. Since the retry logic checks is_timeout() to determine if an error is retryable, hung requests will stall indefinitely instead of timing out and triggering the configured retry mechanism.

Apply retry_config.request_timeout (after adding it to RetryConfig) to the Client::builder() at lines 76-78. Alternatively, if timeout behavior is intentionally deferred to a per-request level, document that decision and remove the unused request_timeout from AgentConfig.

Also applies to: 75-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/client.rs` around lines 27 - 35, The Api client never applies the
configured request timeout — update RetryConfig to include request_timeout
(Duration), extend the impl From<&crate::config::AgentConfig> for RetryConfig to
copy cfg.request_timeout into RetryConfig, and then pass that timeout into the
reqwest client builder inside ApiClient::new by calling
.timeout(retry_config.request_timeout) on Client::builder(); this ensures
hanging requests honor the configured timeout so is_timeout()-based retry logic
can run.

Comment on lines 124 to +130
pub async fn get_next_task(&self) -> Result<NextTaskResponse, ApiError> {
let url = self.url("tasks/next");
let resp = self.authed_post(&url).send().await?;
self.handle_response(resp).await
self.with_retry(|| async {
let url = self.url("tasks/next")?;
let resp = self.authed_post(&url).send().await?;
self.handle_response(resp).await
})
.await
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/api/client.rs | head -150 | tail -50

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 2075


🏁 Script executed:

rg "with_retry" src/api/client.rs -B 5 -A 15

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 4483


🏁 Script executed:

rg "tasks/next" . --type rust

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 211


🏁 Script executed:

fd "client.rs" src/ --exec cat -n {} \; | head -200

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 8026


🏁 Script executed:

rg "idempotent|idempotency|Idempotency-Key" . --type rust

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 54


🏁 Script executed:

rg "NextTaskResponse" . --type rust -B 2 -A 10

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 2038


🏁 Script executed:

rg "authed_post" src/api/client.rs -A 5

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 1090


🏁 Script executed:

cat -n src/api/client.rs | sed -n '200,300p'

Repository: EvilBit-Labs/hash_hive_agent

Length of output: 4453


Add an idempotency key to prevent double-assignment on timeout retry.

A timeout after the server reserves a task but before the client receives the response will trigger a retry, causing a duplicate POST. Without an idempotency key in the request header, the server may assign the task twice or skip work. Either add an Idempotency-Key header (generated once per get_next_task call) to the request, or confirm the server-side endpoint deduplicates based on session+endpoint+timestamp.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/client.rs` around lines 124 - 130, Generate a unique idempotency key
once at the start of get_next_task (e.g., a UUID) and reuse that same key for
every retry attempt so duplicate POSTs are deduplicated; move key generation
outside the with_retry closure and add it to the request headers by setting
"Idempotency-Key" on the request returned from authed_post (before send()),
keeping the rest of the flow (send().await and handle_response) unchanged so
get_next_task, with_retry, authed_post, send, and handle_response continue to be
used as before.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…d status reporting

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…enhance error handling

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Copy link
Copy Markdown

@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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/api/client.rs (3)

18-24: ⚠️ Potential issue | 🟠 Major

Still need to propagate request_timeout into the reqwest client.

AgentConfig already carries request_timeout, but RetryConfig still drops it and ApiClient::new never calls Client::builder().timeout(...). That leaves hung requests outside the retry path, so is_timeout() can never trigger for them.

🛠️ Suggested fix
 pub struct RetryConfig {
     /// Base delay for exponential backoff.
     pub backoff_base: Duration,
     /// Maximum backoff delay cap.
     pub backoff_max: Duration,
     /// Maximum number of retry attempts.
     pub max_retries: u32,
+    /// Per-request HTTP timeout.
+    pub request_timeout: Duration,
 }
@@
         Self {
             backoff_base: cfg.backoff_base,
             backoff_max: cfg.backoff_max,
             max_retries: cfg.max_retries,
+            request_timeout: cfg.request_timeout,
         }
@@
         let http = Client::builder()
             .user_agent(format!("hash_hive_agent/{}", env!("CARGO_PKG_VERSION")))
+            .timeout(retry_config.request_timeout)
             .build()?;

The script below should show request_timeout in src/config/mod.rs and no corresponding .timeout(...) call in src/api/client.rs.

#!/bin/bash
sed -n '17,58p' src/config/mod.rs
sed -n '16,90p' src/api/client.rs
rg -n '\brequest_timeout\b|\.timeout\(' src/config/mod.rs src/api/client.rs src/agent/mod.rs

Also applies to: 27-34, 76-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/client.rs` around lines 18 - 24, RetryConfig currently omits the
request_timeout and ApiClient::new never sets a reqwest client timeout, so
timeouts outside retry logic won't be detected; update the RetryConfig (or the
place where AgentConfig is used) to accept/propagate request_timeout and modify
ApiClient::new to call Client::builder().timeout(request_timeout) when building
the reqwest::Client (use the AgentConfig.request_timeout value), ensuring the
timeout is applied to all requests so is_timeout() can be triggered for hung
requests.

129-135: ⚠️ Potential issue | 🟠 Major

POST /tasks/next still needs an idempotency guarantee under retry.

If the server reserves work and the response is lost or times out, the retry will replay the allocation request and can double-assign or skip a task. Send a stable idempotency key per get_next_task call, or document and test the server-side dedupe rule.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/client.rs` around lines 129 - 135, get_next_task can be retried and
needs a stable idempotency key to avoid double-allocations; generate a single
idempotency key (e.g., a UUID) at the start of get_next_task and pass that same
key into the closure given to with_retry so each retry reuses it, then include
it on the request (e.g., via authed_post header "Idempotency-Key" or equivalent)
before sending; update get_next_task (and tests) to create the idempotency key
outside the async closure and clone/move it into the closure so handle_response
and server-side dedupe can rely on a stable key.

424-445: ⚠️ Potential issue | 🟡 Minor

This connect-error fixture is still environment-dependent.

192.0.2.1:1 can surface as a timeout or another socket error under CI, VPN, or container networking, so asserting is_connect() here can flap. Prefer a local refused-connection fixture, then update GOTCHAS.md to match.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/client.rs` around lines 424 - 445, The test
connect_request_error_is_retryable is flaky because using 192.0.2.1 can produce
different socket errors in CI; change the fixture to create a deterministic
local refused connection by binding a local TcpListener to an ephemeral port,
capturing its port (from the listener returned by std::net::TcpListener::bind),
immediately dropping/closing that listener, then issuing the reqwest GET against
http://127.0.0.1:<that_port>/unreachable and asserting the resulting error is a
connect error wrapped by ApiError::Request and treated as retryable by
is_retryable; also update GOTCHAS.md to describe this new
local-refused-connection approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ARCHITECTURE.md`:
- Line 39: The ARCHITECTURE.md line references an unreachable relative path
(`../hash_hive/packages/openapi/agent-api.yaml`); replace it with a reachable
canonical reference by either: 1) pointing to the server repo and commit/tag URL
(e.g., the GitHub repo and specific commit/hash or release) or 2) explicitly
stating that the contract lives in a sibling repository and documenting that
path and how to obtain it, and update the sentence "All API interactions use the
Agent API v1 contract (see `../hash_hive/packages/openapi/agent-api.yaml`)" to
include the chosen reachable URL or the clear sibling-repo note so readers can
locate the OpenAPI contract.

---

Duplicate comments:
In `@src/api/client.rs`:
- Around line 18-24: RetryConfig currently omits the request_timeout and
ApiClient::new never sets a reqwest client timeout, so timeouts outside retry
logic won't be detected; update the RetryConfig (or the place where AgentConfig
is used) to accept/propagate request_timeout and modify ApiClient::new to call
Client::builder().timeout(request_timeout) when building the reqwest::Client
(use the AgentConfig.request_timeout value), ensuring the timeout is applied to
all requests so is_timeout() can be triggered for hung requests.
- Around line 129-135: get_next_task can be retried and needs a stable
idempotency key to avoid double-allocations; generate a single idempotency key
(e.g., a UUID) at the start of get_next_task and pass that same key into the
closure given to with_retry so each retry reuses it, then include it on the
request (e.g., via authed_post header "Idempotency-Key" or equivalent) before
sending; update get_next_task (and tests) to create the idempotency key outside
the async closure and clone/move it into the closure so handle_response and
server-side dedupe can rely on a stable key.
- Around line 424-445: The test connect_request_error_is_retryable is flaky
because using 192.0.2.1 can produce different socket errors in CI; change the
fixture to create a deterministic local refused connection by binding a local
TcpListener to an ephemeral port, capturing its port (from the listener returned
by std::net::TcpListener::bind), immediately dropping/closing that listener,
then issuing the reqwest GET against http://127.0.0.1:<that_port>/unreachable
and asserting the resulting error is a connect error wrapped by
ApiError::Request and treated as retryable by is_retryable; also update
GOTCHAS.md to describe this new local-refused-connection approach.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04af96bb-f371-4b9b-b5e2-b054d3bdcf6b

📥 Commits

Reviewing files that changed from the base of the PR and between 33c4eb7 and 058c7a0.

📒 Files selected for processing (9)
  • AGENTS.md
  • ARCHITECTURE.md
  • CONTRIBUTING.md
  • GOTCHAS.md
  • src/api/client.rs
  • src/hashcat/session.rs
  • src/task/downloader.rs
  • src/task/runner.rs
  • tests/api_retry_test.rs
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • CONTRIBUTING.md
  • src/task/runner.rs

6. **Error Reporting:** Report agent errors via `POST /errors`.
7. **Zap Lists:** Fetch already-cracked hashes via `GET /tasks/{id}/zaps` to skip during processing.

All API interactions use the Agent API v1 contract (see `../hash_hive/packages/openapi/agent-api.yaml`). All API requests use exponential backoff with jitter for transient failures (see [Retry / Backoff](#retry--backoff)).
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Point this API-contract reference at a reachable source.

../hash_hive/packages/openapi/agent-api.yaml is outside this repository, so readers on GitHub or a standalone checkout cannot follow it. Link the canonical server repo/commit or say explicitly that the contract lives in a sibling repository.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ARCHITECTURE.md` at line 39, The ARCHITECTURE.md line references an
unreachable relative path (`../hash_hive/packages/openapi/agent-api.yaml`);
replace it with a reachable canonical reference by either: 1) pointing to the
server repo and commit/tag URL (e.g., the GitHub repo and specific commit/hash
or release) or 2) explicitly stating that the contract lives in a sibling
repository and documenting that path and how to obtain it, and update the
sentence "All API interactions use the Agent API v1 contract (see
`../hash_hive/packages/openapi/agent-api.yaml`)" to include the chosen reachable
URL or the clear sibling-repo note so readers can locate the OpenAPI contract.

- Add -v/--verbose flag (count-based: -v=debug, -vv=trace)
- Add env var support for --json-logs (HASH_HIVE_JSON_LOGS)
- Add value_name hints and long_about with lifecycle description
- Differentiate exit codes: 1=runtime, 2=config, 3=auth
- Respect NO_COLOR env var and detect TTY for ANSI output
- Warn when using default localhost server URL
- Add actionable troubleshooting hints to config error messages
- Add indicatif progress bars for file downloads (bytes/speed/ETA)
- Include max_retries in retry warning log messages
- Add shutdown progress messages (final heartbeat, clean stop)
- Use system-level default paths (/var/lib, /var/cache on Unix,
  %ProgramData% on Windows) appropriate for a service daemon

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Add service-install, service-uninstall, service-start, service-stop,
and service-status subcommands using the service-manager crate.

Supports systemd (Linux), launchd (macOS), and sc.exe (Windows).
Service install passes through HASH_HIVE_* environment variables and
sets restart-on-failure with a 5s delay. On Windows, the agent relies
on its existing Ctrl+C handler for graceful shutdown — no SCM
integration needed.

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
- Set rust-version to 1.94 (current stable)
- Update sysinfo 0.35 -> 0.38, time 0.3.45 -> 0.3.47 (fixes
  RUSTSEC-2026-0009), and all transitive deps
- Remove stale MSRV-pinning entries from GOTCHAS.md
- Collapse nested if statements using let-chains (Rust 1.87+ feature,
  flagged by clippy on 1.94)

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Copy link
Copy Markdown

@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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/hashcat/session.rs (1)

56-140: ⚠️ Potential issue | 🔴 Critical

start() never turns process completion into SessionEvent::Exit.

Line 140 returns the live Child, so kill_on_drop(true) does not help when cancel trips; the token only stops the reader tasks. There is also no waiter here that turns child.wait() into SessionEvent::Exit. In src/task/runner.rs:64-118, the normal completion path returns only on SessionEvent::Exit, while a closed channel falls through to child.wait() and reports failure. Clean exits are therefore misreported, and cancellation can block until hashcat exits on its own.

Please wire the child lifecycle into one place: either own wait()/kill() here and emit SessionEvent::Exit, or keep wait/kill in runner and remove Exit from this channel contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hashcat/session.rs` around lines 56 - 140, The start() function currently
returns the live Child and never converts process termination into a
SessionEvent::Exit; add a dedicated task inside start() that owns the child
lifecycle: await child.wait(), map the ExitStatus into SessionEvent::Exit and
send it on the mpsc::Sender (tx) so the runner sees clean exits, and also
observe the CancellationToken to call child.kill() (or .start_kill()) on
cancellation before awaiting so cancellation produces an Exit event too; keep
existing stdout/stderr reader tasks (handle_stdout_line, classify_line) but
ensure they don't outlive the exit-sender task and that sending
SessionEvent::Exit handles a closed receiver gracefully.
♻️ Duplicate comments (2)
src/api/client.rs (2)

128-135: ⚠️ Potential issue | 🟠 Major

Retrying POST /tasks/next can still double-assign work.

If the server reserves a task and the client times out before seeing the response, the retry replays the same POST with no request-scoped idempotency key. That is a data-corruption path unless the server explicitly deduplicates repeated pulls for the same session. Please verify the server-side semantics here, or add a stable idempotency key generated once per logical call and reused across retries.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/client.rs` around lines 128 - 135, The POST to "tasks/next" is
retried via with_retry but no idempotency key is set, so duplicate POSTs can
double-assign work; fix by generating a single idempotency key once per logical
get_next_task invocation (e.g., let idempotency_key = Uuid::new_v4()) before
calling with_retry and capture it in the async closure passed to with_retry,
then attach that same key as a request header (e.g., "Idempotency-Key") on the
request created by authed_post(&url) so every retry replays the same idempotency
token; also consider verifying server dedup semantics if present.

18-35: ⚠️ Potential issue | 🟠 Major

The configured request timeout still never reaches reqwest::Client.

AgentConfig still carries request_timeout, but RetryConfig drops it and ApiClient::new() never calls .timeout(...). Hung requests will keep using reqwest's default no-global-timeout behavior instead of becoming retryable is_timeout() failures.

Suggested fix
 pub struct RetryConfig {
     /// Base delay for exponential backoff.
     pub backoff_base: Duration,
     /// Maximum backoff delay cap.
     pub backoff_max: Duration,
     /// Maximum number of retry attempts.
     pub max_retries: u32,
+    /// Per-request timeout applied by the underlying HTTP client.
+    pub request_timeout: Duration,
 }
 
 impl From<&crate::config::AgentConfig> for RetryConfig {
     fn from(cfg: &crate::config::AgentConfig) -> Self {
         Self {
             backoff_base: cfg.backoff_base,
             backoff_max: cfg.backoff_max,
             max_retries: cfg.max_retries,
+            request_timeout: cfg.request_timeout,
         }
     }
 }
@@
     pub fn new(base_url: Url, retry_config: RetryConfig) -> Result<Self, ApiError> {
         let http = Client::builder()
             .user_agent(format!("hash_hive_agent/{}", env!("CARGO_PKG_VERSION")))
+            .timeout(retry_config.request_timeout)
             .build()?;

Also applies to: 76-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/client.rs` around lines 18 - 35, The request timeout from AgentConfig
is being dropped so reqwest never gets a client timeout; update the code to
carry the request_timeout through into RetryConfig (add a pub request_timeout:
Duration field initialized from cfg.request_timeout) and then apply it when
building the HTTP client inside ApiClient::new() by calling the reqwest
Client::builder().timeout(cfg_or_retry.request_timeout) before .build(),
ensuring hung requests become reqwest timeout errors (is_timeout()) and thus
retryable; references: RetryConfig, AgentConfig, request_timeout, and
ApiClient::new.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/config/mod.rs`:
- Around line 168-181: The minute and hour branches currently compute m * 60 and
h * 3600 which can overflow; update those closures to use checked_mul and fail
the parse on overflow. For the 'm' branch replace .map(|m| Duration::from_secs(m
* 60)) with something like .and_then(|m| m.checked_mul(60).ok_or("duration
overflow".to_string()).map(Duration::from_secs)), and similarly for the 'h'
branch use h.checked_mul(3600).ok_or(...).map(Duration::from_secs) so parse
failures and multiplication overflows both return Err(String).

In `@src/main.rs`:
- Around line 26-33: The service subcommand branch discards other CLI options
(parsed by Cli::parse) by only forwarding cli.command to
hash_hive_agent::service::handle, causing service handlers in service.rs to fall
back to env discovery; update the call site to propagate the parsed CLI settings
— either initialize/apply the parsed config/logging options before calling
hash_hive_agent::service::handle or change handle to accept the full Cli or a
Config struct (e.g., pass cli or a new Config built from --config, --server-url,
--agent-token, --hashcat-path, --json-logs, --log-level) so
service-install/service-uninstall use the provided CLI values rather than
environment defaults.
- Around line 101-109: The classify_exit_code function currently treats "invalid
server URL" as a runtime error; update classify_exit_code(err: &anyhow::Error)
to also detect the "invalid server URL" message (or the underlying URL parse
error) and return EXIT_CONFIG for that case so malformed server_url is
classified as a configuration failure; locate the function by name
(classify_exit_code) and add an additional branch or include "invalid server
URL" in the existing config-match condition that returns EXIT_CONFIG.

In `@src/service.rs`:
- Around line 39-65: Remove HASH_HIVE_AGENT_TOKEN from the environment vector so
the agent token is not written into ServiceInstallCtx.environment (avoid
persisting secrets to world-readable service files); in the loop that builds
env_vars drop any entry where key == "HASH_HIVE_AGENT_TOKEN" (or filter it out
before building ServiceInstallCtx), leaving other keys unchanged, and instead
document/implement sourcing the token from a secure store (config file with
0600, platform credential store, or secrets manager) when the service starts.

---

Outside diff comments:
In `@src/hashcat/session.rs`:
- Around line 56-140: The start() function currently returns the live Child and
never converts process termination into a SessionEvent::Exit; add a dedicated
task inside start() that owns the child lifecycle: await child.wait(), map the
ExitStatus into SessionEvent::Exit and send it on the mpsc::Sender (tx) so the
runner sees clean exits, and also observe the CancellationToken to call
child.kill() (or .start_kill()) on cancellation before awaiting so cancellation
produces an Exit event too; keep existing stdout/stderr reader tasks
(handle_stdout_line, classify_line) but ensure they don't outlive the
exit-sender task and that sending SessionEvent::Exit handles a closed receiver
gracefully.

---

Duplicate comments:
In `@src/api/client.rs`:
- Around line 128-135: The POST to "tasks/next" is retried via with_retry but no
idempotency key is set, so duplicate POSTs can double-assign work; fix by
generating a single idempotency key once per logical get_next_task invocation
(e.g., let idempotency_key = Uuid::new_v4()) before calling with_retry and
capture it in the async closure passed to with_retry, then attach that same key
as a request header (e.g., "Idempotency-Key") on the request created by
authed_post(&url) so every retry replays the same idempotency token; also
consider verifying server dedup semantics if present.
- Around line 18-35: The request timeout from AgentConfig is being dropped so
reqwest never gets a client timeout; update the code to carry the
request_timeout through into RetryConfig (add a pub request_timeout: Duration
field initialized from cfg.request_timeout) and then apply it when building the
HTTP client inside ApiClient::new() by calling the reqwest
Client::builder().timeout(cfg_or_retry.request_timeout) before .build(),
ensuring hung requests become reqwest timeout errors (is_timeout()) and thus
retryable; references: RetryConfig, AgentConfig, request_timeout, and
ApiClient::new.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b518812-4083-41cf-bcd9-8ac584cd5e42

📥 Commits

Reviewing files that changed from the base of the PR and between 058c7a0 and 2cefc80.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .cargo/audit.toml
  • Cargo.toml
  • GOTCHAS.md
  • src/agent/mod.rs
  • src/api/client.rs
  • src/cli.rs
  • src/config/defaults.rs
  • src/config/mod.rs
  • src/hashcat/session.rs
  • src/lib.rs
  • src/main.rs
  • src/service.rs
  • src/task/downloader.rs
✅ Files skipped from review due to trivial changes (3)
  • .cargo/audit.toml
  • src/lib.rs
  • Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/task/downloader.rs
  • src/agent/mod.rs

Comment on lines +168 to 181
if let Some(rest) = trimmed.strip_suffix('s') {
rest.trim()
.parse::<u64>()
.map(Duration::from_secs)
.map_err(|e| e.to_string())
} else if let Some(rest) = s.strip_suffix('m') {
} else if let Some(rest) = trimmed.strip_suffix('m') {
rest.trim()
.parse::<u64>()
.map(|m| Duration::from_secs(m * 60))
.map_err(|e| e.to_string())
} else if let Some(rest) = s.strip_suffix('h') {
} else if let Some(rest) = trimmed.strip_suffix('h') {
rest.trim()
.parse::<u64>()
.map(|h| Duration::from_secs(h * 3600))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reject overflow in minute/hour duration parsing.

m * 60 and h * 3600 can wrap on large inputs in release builds, turning an invalid config value into a much smaller timeout/backoff. Use checked_mul and fail the parse instead.

Suggested fix
-        } else if let Some(rest) = trimmed.strip_suffix('m') {
-            rest.trim()
-                .parse::<u64>()
-                .map(|m| Duration::from_secs(m * 60))
-                .map_err(|e| e.to_string())
-        } else if let Some(rest) = trimmed.strip_suffix('h') {
-            rest.trim()
-                .parse::<u64>()
-                .map(|h| Duration::from_secs(h * 3600))
-                .map_err(|e| e.to_string())
+        } else if let Some(rest) = trimmed.strip_suffix('m') {
+            let minutes = rest.trim().parse::<u64>().map_err(|e| e.to_string())?;
+            let secs = minutes
+                .checked_mul(60)
+                .ok_or_else(|| format!("duration overflow: {trimmed}"))?;
+            Ok(Duration::from_secs(secs))
+        } else if let Some(rest) = trimmed.strip_suffix('h') {
+            let hours = rest.trim().parse::<u64>().map_err(|e| e.to_string())?;
+            let secs = hours
+                .checked_mul(3600)
+                .ok_or_else(|| format!("duration overflow: {trimmed}"))?;
+            Ok(Duration::from_secs(secs))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/mod.rs` around lines 168 - 181, The minute and hour branches
currently compute m * 60 and h * 3600 which can overflow; update those closures
to use checked_mul and fail the parse on overflow. For the 'm' branch replace
.map(|m| Duration::from_secs(m * 60)) with something like .and_then(|m|
m.checked_mul(60).ok_or("duration
overflow".to_string()).map(Duration::from_secs)), and similarly for the 'h'
branch use h.checked_mul(3600).ok_or(...).map(Duration::from_secs) so parse
failures and multiplication overflows both return Err(String).

Comment on lines +26 to +33
// Service management subcommands run without logging/config.
if let Some(ref cmd) = cli.command {
if let Err(e) = hash_hive_agent::service::handle(cmd) {
eprintln!("error: {e:#}");
std::process::exit(EXIT_RUNTIME);
}
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Service subcommands currently discard every other CLI option.

Cli::parse() still accepts --config, --server-url, --agent-token, --hashcat-path, --json-logs, and --log-level, but this branch only forwards cli.command. src/service.rs then falls back to environment discovery, so a call like hash_hive_agent --config /etc/agent.toml service-install installs a service that will not use the supplied CLI values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 26 - 33, The service subcommand branch discards
other CLI options (parsed by Cli::parse) by only forwarding cli.command to
hash_hive_agent::service::handle, causing service handlers in service.rs to fall
back to env discovery; update the call site to propagate the parsed CLI settings
— either initialize/apply the parsed config/logging options before calling
hash_hive_agent::service::handle or change handle to accept the full Cli or a
Config struct (e.g., pass cli or a new Config built from --config, --server-url,
--agent-token, --hashcat-path, --json-logs, --log-level) so
service-install/service-uninstall use the provided CLI values rather than
environment defaults.

… downloads

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…installation

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
… and config issues

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
- Replace string-based exit code classification with error downcasting
  (ApiError::Auth, config::ConfigError) for type-safe exit codes
- Use alternate Display format ({:#}) for server error reports to
  include full anyhow error chain instead of just top-level message
- Warn on invalid --log-level values instead of silently falling back
- Surface unexpected service stop errors during uninstall instead of
  silently dropping all errors
- Simplify service.rs env var collection with iterator chain
- Remove dead _results computation in task runner
- Collapse nested ifs into let-chains per clippy

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
src/main.rs (1)

117-122: ⚠️ Potential issue | 🟠 Major

Add invalid server URL to config error classification.

The string-matching fallback handles "agent token is required" and "failed to load configuration", but the URL parse error context ("invalid server URL") from src/agent/mod.rs is not covered. This causes bad URLs to return EXIT_RUNTIME instead of EXIT_CONFIG.

🐛 Proposed fix
     let msg = format!("{err:#}");
-    if msg.contains("agent token is required") || msg.contains("failed to load configuration") {
+    if msg.contains("agent token is required")
+        || msg.contains("failed to load configuration")
+        || msg.contains("invalid server URL")
+    {
         return EXIT_CONFIG;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 117 - 122, The fallback error-classification block
that builds msg (let msg = format!("{err:#}")) should also detect the "invalid
server URL" context and map it to EXIT_CONFIG; update the conditional that
checks msg.contains("agent token is required") || msg.contains("failed to load
configuration") to include msg.contains("invalid server URL") so URL parse
errors from src/agent/mod.rs are classified as configuration failures and return
EXIT_CONFIG.
🧹 Nitpick comments (5)
src/task/downloader.rs (2)

119-121: The buf.get(..n) check is redundant.

Since n is returned from reader.read(&mut buf), it's guaranteed that n <= buf.len(). The slice &buf[..n] will always be valid, making the if let Some(chunk) check unnecessary.

🔧 Simplify to direct slice
         if n == 0 {
             break;
         }
-        if let Some(chunk) = buf.get(..n) {
-            hasher.update(chunk);
-        }
+        hasher.update(&buf[..n]);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/task/downloader.rs` around lines 119 - 121, The conditional using
buf.get(..n) is redundant because n is the byte count returned by reader.read
and always <= buf.len(); replace the if-let block with a direct slice update by
calling hasher.update(&buf[..n]) (locate this change near the read loop where
buf, n and hasher.update are used in downloader.rs) so the code directly updates
the hasher with the read bytes without the unnecessary Option check.

46-67: Consider disabling progress bars when stderr is not a TTY.

In daemon/service mode or CI pipelines, the progress bar output to stderr may interfere with structured logging or produce excessive noise. indicatif supports hidden progress bars via ProgressBar::hidden() when !std::io::stderr().is_terminal().

💡 Optional: TTY-aware progress bar creation
     let total_size = resp.content_length().unwrap_or(0);
+    let is_tty = std::io::stderr().is_terminal();
     let progress = match total_size {
-        0 => {
+        _ if !is_tty => ProgressBar::hidden(),
+        0 => {
             let pb = ProgressBar::new_spinner();
             // ... rest unchanged

This requires adding use std::io::IsTerminal; (stable since Rust 1.70).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/task/downloader.rs` around lines 46 - 67, The progress bar is always
created and may spam stderr in non-TTY environments; update the progress
construction in the total_size match (the total_size variable and progress
binding) to check stderr TTY via std::io::stderr().is_terminal() (add use
std::io::IsTerminal) and use ProgressBar::hidden() when not a terminal instead
of ProgressBar::new_spinner()/ProgressBar::new(n), preserving the existing
styles for the TTY case (references: ProgressBar::new_spinner, ProgressBar::new,
ProgressBar::hidden, ProgressStyle::default_spinner,
ProgressStyle::default_bar).
src/service.rs (2)

24-32: Add wildcard arm for #[non_exhaustive] enum.

The Command enum in src/cli.rs is marked #[non_exhaustive], which means the compiler will not enforce exhaustive matching. If a new variant is added to Command, this match will silently fail to compile only within the same crate — external consumers would get a runtime mismatch. Since this is the same crate, you're currently safe, but adding a wildcard with an explicit error improves defensive coding.

🛡️ Suggested defensive pattern
 pub fn handle(command: &Command) -> Result<()> {
     match *command {
         Command::ServiceInstall => install(),
         Command::ServiceUninstall => uninstall(),
         Command::ServiceStart => start(),
         Command::ServiceStop => stop(),
         Command::ServiceStatus => status(),
+        #[allow(unreachable_patterns)]
+        _ => anyhow::bail!("unhandled service command"),
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/service.rs` around lines 24 - 32, The match in function handle on the
non-exhaustive Command enum lacks a wildcard arm; update handle (the match over
Command with arms calling install(), uninstall(), start(), stop(), status()) to
include a final _ => Err(anyhow::anyhow!("unsupported command variant")) (or
equivalent error) so unknown future variants return a clear Result::Err instead
of causing unexpected behavior; keep existing arms intact and ensure the error
message names the function (handle) and the enum (Command) for clarity.

53-67: Consider documenting the --json-logs default.

The service is installed with --json-logs hardcoded. This is a reasonable default for services (structured logs are easier to parse), but users may expect text logs. Consider adding a brief comment explaining this choice or making it configurable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/service.rs` around lines 53 - 67, The ServiceInstallCtx is hardcoding
"--json-logs" in args which should be either documented or made configurable;
update the code around the ServiceInstallCtx construction to either (a) add a
short comment above the ctx explaining why "--json-logs" is chosen (structured
logs for parsing/monitoring) or (b) make it configurable by introducing a
boolean flag (e.g., enable_json_logs) passed into the surrounding function and
use it to set args (args: enable_json_logs.then(||
vec![OsString::from("--json-logs")]).ensure a sensible default (true) and
propagate the flag through callers so users can opt out, referencing
ServiceInstallCtx and the args field when making the change.
src/task/runner.rs (1)

133-133: Add validation guard to prevent negative work range.

The #[allow(clippy::arithmetic_side_effects)] annotation suppresses warnings but does not prevent the arithmetic error. Line 149 computes range.end - range.start without checking that range.end >= range.start. If the server sends an invalid range, this will wrap and produce a very large limit value passed to hashcat.

Add a debug assertion:

Defensive validation
     if let Some(ref range) = task.work_range {
+        debug_assert!(range.end >= range.start, "invalid work range");
         args.push("--skip".to_owned());
         args.push(range.start.to_string());
         args.push("--limit".to_owned());
         args.push((range.end - range.start).to_string());
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/task/runner.rs` at line 133, Add a defensive validation for the work
range before performing the subtraction used to compute the limit: check that
range.end >= range.start (e.g., with a debug_assert! and/or an early
error/return) prior to the computation `range.end - range.start` in the task
runner where the limit for hashcat is derived; if the check fails, return an
appropriate error or refuse the job instead of letting the subtraction wrap to a
huge value. Ensure the check is placed near the existing clippy attribute and
references the same `range` variable so the downstream code that builds the
hashcat limit uses a validated, non-negative value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main.rs`:
- Around line 117-122: The fallback error-classification block that builds msg
(let msg = format!("{err:#}")) should also detect the "invalid server URL"
context and map it to EXIT_CONFIG; update the conditional that checks
msg.contains("agent token is required") || msg.contains("failed to load
configuration") to include msg.contains("invalid server URL") so URL parse
errors from src/agent/mod.rs are classified as configuration failures and return
EXIT_CONFIG.

---

Nitpick comments:
In `@src/service.rs`:
- Around line 24-32: The match in function handle on the non-exhaustive Command
enum lacks a wildcard arm; update handle (the match over Command with arms
calling install(), uninstall(), start(), stop(), status()) to include a final _
=> Err(anyhow::anyhow!("unsupported command variant")) (or equivalent error) so
unknown future variants return a clear Result::Err instead of causing unexpected
behavior; keep existing arms intact and ensure the error message names the
function (handle) and the enum (Command) for clarity.
- Around line 53-67: The ServiceInstallCtx is hardcoding "--json-logs" in args
which should be either documented or made configurable; update the code around
the ServiceInstallCtx construction to either (a) add a short comment above the
ctx explaining why "--json-logs" is chosen (structured logs for
parsing/monitoring) or (b) make it configurable by introducing a boolean flag
(e.g., enable_json_logs) passed into the surrounding function and use it to set
args (args: enable_json_logs.then(|| vec![OsString::from("--json-logs")]).ensure
a sensible default (true) and propagate the flag through callers so users can
opt out, referencing ServiceInstallCtx and the args field when making the
change.

In `@src/task/downloader.rs`:
- Around line 119-121: The conditional using buf.get(..n) is redundant because n
is the byte count returned by reader.read and always <= buf.len(); replace the
if-let block with a direct slice update by calling hasher.update(&buf[..n])
(locate this change near the read loop where buf, n and hasher.update are used
in downloader.rs) so the code directly updates the hasher with the read bytes
without the unnecessary Option check.
- Around line 46-67: The progress bar is always created and may spam stderr in
non-TTY environments; update the progress construction in the total_size match
(the total_size variable and progress binding) to check stderr TTY via
std::io::stderr().is_terminal() (add use std::io::IsTerminal) and use
ProgressBar::hidden() when not a terminal instead of
ProgressBar::new_spinner()/ProgressBar::new(n), preserving the existing styles
for the TTY case (references: ProgressBar::new_spinner, ProgressBar::new,
ProgressBar::hidden, ProgressStyle::default_spinner,
ProgressStyle::default_bar).

In `@src/task/runner.rs`:
- Line 133: Add a defensive validation for the work range before performing the
subtraction used to compute the limit: check that range.end >= range.start
(e.g., with a debug_assert! and/or an early error/return) prior to the
computation `range.end - range.start` in the task runner where the limit for
hashcat is derived; if the check fails, return an appropriate error or refuse
the job instead of letting the subtraction wrap to a huge value. Ensure the
check is placed near the existing clippy attribute and references the same
`range` variable so the downstream code that builds the hashcat limit uses a
validated, non-negative value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e11a17d-f0d8-45a1-b917-77daec3d9a92

📥 Commits

Reviewing files that changed from the base of the PR and between 2cefc80 and 98875d4.

📒 Files selected for processing (4)
  • src/main.rs
  • src/service.rs
  • src/task/downloader.rs
  • src/task/runner.rs

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
@unclesp1d3r unclesp1d3r changed the title feat: Implement exponential backoff with jitter for API requests feat: exponential backoff, CLI UX, service management, and doc restructure Mar 28, 2026
…r AI-assisted contributions

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:api API client and server communication area:config Configuration and CLI area:testing Test infrastructure and coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement exponential backoff with jitter for API requests

2 participants