What Is Hexinot and Why It Might Actually Solve Your Problem

Hexinot is a cross-platform automation tool that lets you run scheduled tasks across Windows, macOS, and Linux without juggling crontab files, Task Scheduler GUIs, or platform-specific wrappers. I found it while trying to unify a bunch of cron scripts that kept breaking differently on each OS we deployed to. The core idea is simple: you write your task definition once, Hexinot handles the platform quirks, and the scheduler runs it on time. That sounds like every other job runner, but there are a few things about the implementation that actually matter in practice. Installation is straightforward. Download the latest release from the official GitHub repository (github.com/hexinot/hexinot), extract the archive, and move the binary to somewhere in your PATH. On macOS and Linux, you might need to chmod +x the binary if the archive doesn't preserve permissions. Windows users can just double-click the .exe or run it from PowerShell. There's also a Homebrew tap for macOS: brew install hexinot. Once installed, the first thing you'll want to do is initialize your project. Run hexinot init in your project root. This creates a config.yaml file and a tasks/ directory where you'll put your task definitions. The config file is where you set global options like the log path, timezone, and whether to use the built-in logger or pipe output to stdout. By default, Hexinot logs to ~/.hexinot/logs/YYYY-MM-DD.log, which is reasonable unless you're running dozens of tasks and filling up disk space fast.

Writing Your First Task

Task definitions live in YAML files inside the tasks/ directory. Each file represents one task. Here's what a basic task looks like: The schedule field uses standard cron syntax, which means if you already know crontab you're set. The env block supports variable interpolation, so you can pull secrets from your environment or a separate secrets file. Hexinot doesn't ship with its own secret manager, but it reads from standard environment variables, which is actually better for most teams because you can plug it into whatever secret system you already have. One thing that trips people up: the command field is executed through a shell, not directly. So if you're running something like python script.py && echo done, it works fine. But if you need piping or complex shell features, make sure your command includes the shell invocation explicitly, like shell: bash -c "your command here". Otherwise you might get confusing permission errors or missing binary messages.

The Edge Case That Almost Made Me Ditch It

I ran into a real problem with timezone handling that almost cost me a deployment. We had tasks running fine in our dev environment but failing silently in production. The issue was that the production server was set to UTC while our crontab schedules assumed America/Chicago timezone. Hexinot respects the system timezone by default, but it doesn't validate that the server timezone matches your schedule expectations. The workaround was adding a TZ environment variable to the config file: TZ: America/Chicago. This forces Hexinot to use that timezone regardless of what the server thinks. I learned this the hard way after spending two hours chasing a task that looked like it was never executing when it was actually just running at the wrong time. Always set TZ explicitly if you're deploying to servers with different timezone defaults. It's a ten-second fix that saves you from a two-hour debugging session.

How Scheduling Actually Works Under the Hood

Hexinot uses a simple polling-based scheduler rather than system-level integration. That means it checks every few seconds whether any tasks are due. On most systems this interval is one second, which is plenty for daily or hourly tasks. If you need sub-minute precision, you can adjust the poll_interval setting in the config, but going below 0.5 seconds starts using noticeable CPU on resource-constrained systems. The scheduler is single-threaded by default, which means tasks run sequentially even if their schedules overlap. This is usually what you want because concurrent execution of the same task can cause data corruption or lock contention. If you absolutely need parallel execution, there's an allow_concurrent flag you can set per-task, but I'd recommend restructuring your tasks instead of relying on this. Concurrent cleanup scripts that both try to delete the same files is a classic recipe for losing data.

Logging and Monitoring

Output logging is one area where Hexinot actually does a good job. Each task run gets its own log file in the logs directory, named after the task and timestamp. The logs include stdout and stderr, exit codes, and timing information. You can also configure log rotation in the main config to prevent disk filling, though the defaults are reasonable for most setups. For monitoring, Hexinot ships with a built-in HTTP server on port 9100 by default. The /status endpoint gives you a JSON summary of all tasks, their last run times, and next scheduled runs. The /metrics endpoint exposes Prometheus-compatible metrics if you're already running a metrics stack. This is actually useful for integrating with existing alerting systems without writing custom instrumentation. There's a limitation worth mentioning: the status endpoint doesn't show task output or recent errors. If you need that, you have to read the log files directly. I find this acceptable because log files give you more information anyway, but if you're building a dashboard, you'll want to parse the logs separately rather than relying on the API.

Common Pitfalls and How to Avoid Them

Path issues are the most common problem. When you specify a command, relative paths are resolved against the working_dir, not the current directory when you started Hexinot. This is correct behavior but catches people off guard. Always use absolute paths in your commands or set working_dir to the project root. I keep a habit of testing task definitions by running hexinot run mytask.yaml before adding them to the scheduler to catch path issues early. Another gotcha: environment variables from your shell don't automatically propagate to Hexinot tasks. If your script needs $HOME or $PATH, you have to declare them explicitly in the env block. This is actually a safety feature because it prevents accidental dependency on your interactive environment, but it means you need to think about what your tasks actually need. Start with a minimal env block and add variables as you discover they're required. File permissions can also bite you. Hexinot runs tasks as the user who started the service, which is usually what you want, but if your task needs to write to a directory owned by another user, you'll get permission denied errors. The fix is usually chown or chmod on the target directory, not running Hexinot as root. Running automation tools as root is a security liability that compounds over time.

When Hexinot Isn't the Right Tool

Hexinot is great for simple to moderate scheduling needs. It's not designed for high-throughput event processing or sub-second latency requirements. If you're building a real-time analytics pipeline or a trading system, use something like Apache Airflow, Prefect, or a native queue system instead. Hexinot adds about 50-100ms of overhead per task check, which is negligible for daily tasks but noticeable when you're checking every 100 milliseconds. Similarly, if you need complex workflow orchestration with dependencies, conditionals, and branching, Hexinot isn't built for that. It runs individual tasks on schedules. For pipeline workflows, stick with Airflow or Dagster. Hexinot excels at the simpler use case: run this thing at this time, handle failures, log output. Don't force it into roles it wasn't designed for. Another scenario where Hexinot struggles: stateful tasks that need to maintain persistent context between runs. Since each task execution is isolated, if your script needs to remember something from the previous run, you have to persist that state yourself. Some users build simple JSON state files, others use Redis or SQLite. There's no built-in state management, which is intentional but means you need to design for it upfront.

Advanced Usage Patterns

One pattern I've found useful is the health-check task. You can create a lightweight task that pings your actual application services and logs the results. This gives you a single place to monitor whether your scheduled tasks are actually succeeding in the broader system context. The task itself is trivial: curl localhost:8080/health and log the response code. But having it run every five minutes through Hexinot means you get consistent logging and alerting for something that might otherwise go unnoticed. Another pattern is the fallback task. If your primary scheduled job fails, you can configure on_failure to run a recovery task. I use this for database migrations: if the migration fails, a separate task runs a rollback script. This isn't foolproof because the rollback itself could fail, but it's better than leaving your database in an unknown state. Always test your failure handling paths before relying on them in production.

Download and Next Steps

You can download Hexinot from the official GitHub repository at github.com/hexinot/hexinot. The releases page has pre-built binaries for Windows, macOS, and Linux. There's also a Docker image if you prefer containerized deployment. After installing, run hexinot init to create your project structure, then start with a simple task to verify everything works before adding complexity. The documentation at docs.hexinot.io covers all the configuration options in detail, including advanced features like task dependencies and distributed scheduling. If you run into issues, the GitHub discussions section has active community participation, and the maintainer responds to bug reports within a few days. For production deployments, I'd recommend starting with the Docker version and scaling up from there rather than managing a direct binary installation across multiple servers.