iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
A senro pipeline is declared in Go, resolved into a plan, and then executed. To build a practical one, make file flow explicit with workspaces, mark only genuinely deterministic steps as cacheable, expand work across repository units, and use triggers to decide whether an incoming event should run the pipeline. The examples below follow senro v1.4.0 as described in the author’s walkthrough; check API details and defaults against the version you install.
How a senro pipeline is structured
senro’s package documentation describes a plan as an immutable directed acyclic graph (DAG): steps are nodes, and dependency declarations specify which work must finish first. A run executes that plan and records an append-only event stream. Workflows group related steps. The documented execution targets include local processes, containers, Kubernetes pods, and remote hosts over SSH. See the senro package documentation.
The v1.4.0 walkthrough uses a simple build flow: clone a repository into a named src workspace, run vet and test after checkout, then build after both checks finish. Checkout writes the shared source; the checks read it; the build waits for both checks and writes its output.
Recommended Free Tools
checkout → vet ─┐
├→ build
test ─┘
The graph makes both ordering and data flow explicit. vet and test can be independent of each other after checkout, while build cannot start until both complete.
#1 Best Overall
How to use workspaces and mounts
A workspace is a named directory made available to steps. Choose its lifetime and mount permissions according to who needs the files and whether they should be changed.
| Workspace scope | Lifetime | Good fit |
|---|---|---|
| Run-scoped | Shared by steps within one run | Checked-out source or intermediate files used by several steps |
| Persistent | Outlives a run on the same machine | Data intentionally retained between runs; set explicit bounds |
| Step-scoped | Private to one step | Temporary files that should not be shared |
Use read-only mounts for consumers such as tests and static checks, and read/write mounts for checkout or steps that create outputs. This both communicates intent and avoids giving a step unnecessary write access.
Do not assume the pipeline builder supplies a complete shell environment. The walkthrough says Build() does not add HOME or GOPATH; pass required environment values explicitly. The local executor supplies its own PATH.
Read-only behavior depends on the executor
Containers and Kubernetes enforce read-only mounts when a write is attempted. Local and SSH execution detect writes after the fact or when data is read back, according to the walkthrough. A read-only declaration therefore does not imply identical kernel-level enforcement across all execution targets.
How senro caches a step
Calling Pure() asserts that the declared inputs and step configuration determine the output. When the cache key matches a recorded result, senro can restore the outputs instead of executing the step again. As the walkthrough’s author, Xavier Portilla Edo, puts it: “Pure() is a promise: given these inputs, this command produces these outputs, and nothing else matters.”
That promise is only sound if the step does not depend on undeclared or variable factors. Consider the command, environment, inputs, workspaces, mount configuration, and other parts of the key. If an external state can change the result but is not reflected in the key, a cache hit can return stale output.
Rank #3
Workspace size can broaden the cache key
In the described behavior, mounting a workspace adds the entire workspace to the cache key. Narrower Inputs declarations do not remove that workspace contribution. For expensive work performed independently on monorepo units, use separate unit workspaces where practical; mounting one large shared tree can make unrelated changes invalidate more work.
The walkthrough demonstrates cache hits and misses, including a miss report showing changed source and workspace digests. Those are examples from its sample repository, not general performance measurements.
Using a remote cache
The package documentation describes shared remote caching as a second tier behind the local cache, with S3-compatible object storage and an OCI registry repository among the supported options. A remote cache can help fresh CI runners reuse results produced elsewhere. The documentation establishes the backend categories, not a preferred provider or a performance guarantee.
Rank #4
How to expand a pipeline across monorepo units
Expand(id, graph) creates a step for each unit discovered by a graph. The v1.4.0 walkthrough lists these graph families:
- Glob-based directories or files
- Go workspace modules
- Rust Cargo crates
- npm, pnpm, or Yarn workspaces
- Maven and Gradle modules
- Python projects identified by
pyproject - Bazel packages
Ecosystem-aware graphs read project manifests, which can provide dependency relationships in addition to identifying units. A glob graph can locate paths, but it cannot infer code dependencies by itself.
How to run only affected units
Affected(src) narrows expansion to units that own changed files and units that depend on those units, including transitive dependents. In the walkthrough’s example, changing a shared library also selects dependent services that need to be rebuilt or checked.
Affected selection needs a dependency-aware graph. A path match alone cannot establish which modules import or depend on a changed unit, so the documented implementation refuses affected selection for a glob graph rather than claiming to know its dependents.
Keep expansion bounded
Expansion is resolved when the plan is built, not gradually as steps execute. Bound parallel work and node count so a large repository does not produce an unexpectedly large plan. The walkthrough gives a default maximum of 500 expanded nodes for its described version; verify the current default in the version you use.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.How triggers decide whether a run starts
The pipeline binary can match a supplied event against declared triggers. The package documentation describes event kinds including push, pull request, tag, schedule, and manual. Its built-in webhook sources include GitHub, GitLab, Bitbucket, and Gitea, and it also supports caller-defined providers. Trigger configuration and matching behavior are documented in the senro trigger package.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsA trigger match runs the pipeline. A no-match is a separate sentinel outcome, conventionally mapped by the caller to exit code 78; it means the event was declined, not that pipeline parsing failed. The walkthrough’s example accepts a push to the main branch, distinguishes other pushes and pull requests, and declines a tag event with a reason. Treat a parse or configuration error as a wiring problem to investigate, rather than as an ordinary no-match.
Operational details that can change results
Keep run data outside filesystem discovery paths
In the described setup, senro writes run data under runs/<id>/ by default. If that directory sits inside a tree scanned by a filesystem graph, an earlier run’s checkout may be discovered as another copy of the modules. Adding the path to .gitignore does not stop a filesystem scan; place run data outside the discovery tree or configure discovery to avoid it.
Do not generalize sample timings
The walkthrough includes timings from its sample repository, but it is not a representative benchmark study. Actual duration depends on the repository, step commands, cache state, executor, and machine. Treat those timings as illustrations of that run, not as a prediction for another project.
Quick Recap
A practical design checklist
- Declare checkout and downstream steps as dependencies so the plan captures required ordering.
- Use run-scoped workspaces for shared per-run data, and choose mount permissions that match each step’s role.
- Provide environment values such as
HOMEorGOPATHwhen tools require them. - Mark a step with
Pure()only when its declared inputs and configuration fully determine its outputs. - Use unit-level workspaces when whole-workspace cache keys would make unrelated monorepo changes invalidate expensive work.
- Choose ecosystem-aware graphs for dependency-aware affected runs; use glob graphs when path discovery is sufficient.
- Bound expanded nodes and parallelism, and keep run data outside directories scanned for units.
- Handle trigger no-match separately from parse or configuration errors.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Free tools Windows power users keep installed
One-click scans. No signup required.

