• English
  • OS Integration in Go: Managing Processes, Signals, Environment, and System Boundaries

    TL;DR

    Go is designed to make operating-system integration practical without forcing every application to become system software.

    For most services, the standard library provides enough:

    • os for files, environment, and process primitives
    • os/exec for starting external programs
    • os/signal for shutdown handling
    • io interfaces for separating application logic from OS resources

    However, once a Go program starts managing other processes, runs inside containers, builds supervisors, or interacts with platform-specific features, the operating system model becomes part of the application design.

    The important concepts are not individual APIs. They are ownership and lifecycle:

    • Who owns this process?
    • Who waits for it?
    • Who handles its failure?
    • Which environment does it inherit?
    • Which signals should reach it?
    • Does an integer PID still identify the same process?
    • When does the portable abstraction stop being enough?

    Production Go systems succeed by staying at the highest abstraction level that solves the problem, and only crossing into native OS APIs when the operating system behavior itself becomes a requirement.


    1. The OS Boundary in Go

    A Go application usually sits between business logic and operating-system resources.

    A simplified model looks like this:

    Application Logic
    
            |
            |
            v
    
    Standard Interfaces
    (io.Reader, io.Writer, fs.FS)
    
            |
            |
    
    Operating-System Abstractions
    (os, os/exec, os/signal)
    
            |
            |
    
    Kernel Resources
    (files, processes, signals, handles)

    The standard library deliberately does not hide the operating system completely.

    Instead, Go provides portable primitives and leaves platform-specific details visible when they matter.

    For example:

    f, err := os.Open("config.yaml")

    looks simple, but underneath it involves:

    • path resolution
    • filesystem permissions
    • file descriptors
    • kernel resource ownership

    Likewise:

    cmd := exec.Command("worker")

    creates a relationship between two processes:

    • a parent process that starts and supervises
    • a child process with its own lifecycle

    The API is simple.

    The semantics are not.


    2. A Process Is a Resource With Ownership

    Starting another process is not equivalent to calling another function.

    A function call stays inside the same runtime:

    main()
     |
     v
    function()
     |
     v
    return

    A process creates an independent execution environment:

    parent process
    
          |
          | start
          v
    
    child process
    
          |
          | exit
          v
    
    exit status

    Once a Go program starts a child process, it becomes responsible for deciding:

    • how the process is configured
    • how long it should live
    • how failures are handled
    • how shutdown is performed
    • how exit status is collected

    A common production mistake is treating process creation as fire-and-forget:

    exec.Command("worker").Start()

    and then forgetting about it.

    The result can be:

    • orphan processes
    • zombie processes
    • leaked CPU and memory
    • unavailable ports
    • incomplete cleanup

    A process is a resource.

    It needs ownership just like:

    • files
    • network connections
    • database transactions

    3. Starting Processes With os/exec

    The standard way to create child processes in Go is os/exec.

    Example:

    cmd := exec.Command(
        "worker",
        "--port",
        "8080",
    )
    
    if err := cmd.Start(); err != nil {
        return err
    }
    
    if err := cmd.Wait(); err != nil {
        return err
    }

    The important part is not Start().

    It is Wait().

    Wait() performs several responsibilities:

    • collects the child exit status
    • releases process resources
    • prevents zombie accumulation on Unix systems

    A child process that has exited but has not been waited on is not fully cleaned up.


    Avoid Shell Wrappers

    A common pattern in scripting environments is:

    sh -c "worker --port 8080"

    The equivalent Go code:

    exec.Command(
        "sh",
        "-c",
        commandString,
    )

    is usually the wrong abstraction.

    It introduces another parsing layer:

    Go program
    
        |
        v
    
    shell parser
    
        |
        v
    
    target program

    This creates several problems:

    • command injection risk
    • quoting complexity
    • different shell behavior across platforms
    • less predictable signal handling

    Prefer passing arguments directly:

    cmd := exec.Command(
        "worker",
        "--port",
        "8080",
    )

    The operating system receives exactly the arguments the program intended.


    4. Managing Process Lifetime With Context

    For long-running services, manually calling Kill() is often not enough.

    Modern Go programs usually connect process lifetime to context.Context.

    Example:

    ctx, cancel := context.WithTimeout(
        context.Background(),
        30*time.Second,
    )
    
    defer cancel()
    
    cmd := exec.CommandContext(
        ctx,
        "worker",
    )
    
    if err := cmd.Run(); err != nil {
        return err
    }

    CommandContext provides a natural relationship:

    context lifetime
    
            |
            v
    
    child process lifetime

    When the context is cancelled:

    cancel()

    the command receives termination.

    This pattern fits naturally with modern Go services where cancellation already controls:

    • HTTP requests
    • background workers
    • database operations
    • shutdown workflows

    A process should usually have the same lifecycle model as the operation that created it.


    5. Process Termination: Graceful First, Forced Later

    Production systems should distinguish between:

    request shutdown

    and:

    force termination

    On Unix systems:

    cmd.Process.Signal(syscall.SIGTERM)

    requests graceful termination.

    A well-behaved child process can:

    • finish current work
    • flush buffers
    • close connections
    • release resources

    A forced kill:

    cmd.Process.Kill()

    does not provide that opportunity.

    The typical escalation strategy is:

    SIGTERM
    
        |
        | wait grace period
    
        v
    
    SIGKILL

    For example:

    0s     send SIGTERM
    
    10s    still running
    
    10s    send SIGKILL

    This is the model used by many production supervisors and container platforms.


    6. A Real Production Failure Scenario

    Consider a Go service that processes media files.

    The architecture:

    API service (Go)
    
          |
          |
          v
    
    ffmpeg worker process
    
          |
          v
    
    temporary files

    A developer writes:

    cmd := exec.Command(
        "ffmpeg",
        input,
    )
    
    cmd.Start()

    Everything works locally.

    Six months later, production has problems:

    • CPU usage slowly increases
    • old ffmpeg processes remain alive
    • deployments take longer
    • containers exceed memory limits

    The root cause:

    The parent service never established ownership.

    There was no:

    cmd.Wait()

    No timeout.

    No shutdown handling.

    No cleanup strategy.

    The code created processes, but never managed them.

    This is the difference between:

    starting a process

    and:

    operating a process

    7. Process Creation Should Define the Boundary

    A clean design keeps OS concerns near the edge.

    Instead of spreading:

    exec.Command(...)

    through application code:

    business logic
    
       |
       |
       +---- exec.Command()
       |
       +---- environment lookup
       |
       +---- filesystem paths

    prefer:

    business logic
    
            |
            v
    
    worker interface
    
            |
            v
    
    process implementation
    
            |
            v
    
    os/exec

    For example:

    type Worker interface {
        Run(ctx context.Context, input string) error
    }

    The business layer knows:

    run this job

    The infrastructure layer knows:

    this means starting a process

    This separation makes testing easier and prevents operating-system details from leaking everywhere.


    8. Process Creation Is Also Environment Creation

    A child process does not start from an empty state.

    It inherits execution context from its parent.

    That includes:

    • environment variables
    • working directory
    • file descriptors
    • user permissions

    These inherited values are often the source of production-only bugs.

    A process launched locally may work because:

    developer machine
    
    HOME=/Users/alice
    PATH=/usr/local/bin
    cwd=/project

    while production has:

    container
    
    HOME=/root
    PATH=/usr/bin
    cwd=/

    The program is identical.

    The execution environment is not.

    9. Signal Handling Is Part of Process Design

    Signals are the operating system's mechanism for delivering asynchronous events to processes.

    They are not ordinary function calls.

    A signal can arrive:

    • while the program is processing a request
    • while a goroutine is waiting
    • while a file operation is running
    • while background work is executing

    A production service must decide which signals matter and how they affect application state.

    The most common lifecycle signals are:

    SignalPurposeTypical Handling
    SIGINTTerminal interrupt (Ctrl+C)Graceful shutdown
    SIGTERMTermination requestNormal production shutdown
    SIGKILLImmediate terminationCannot be handled
    SIGHUPTerminal disconnect / reload requestCustom application behavior

    The important distinction:

    SIGTERM
    
        means:
    
    "Please stop"
    
    not:
    
    "Terminate immediately"

    A well-designed service treats shutdown as a workflow.

    For example:

    receive SIGTERM
    
            |
            v
    
    stop accepting new requests
    
            |
            v
    
    finish active work
    
            |
            v
    
    close resources
    
            |
            v
    
    exit

    10. Use signal.NotifyContext for Modern Go Services

    Older Go programs often use:

    sigChan := make(chan os.Signal, 1)
    
    signal.Notify(
        sigChan,
        syscall.SIGTERM,
        syscall.SIGINT,
    )
    
    <-sigChan
    
    shutdown()

    This works, but modern Go applications usually integrate signals with context.

    A typical pattern:

    ctx, stop := signal.NotifyContext(
        context.Background(),
        syscall.SIGTERM,
        syscall.SIGINT,
    )
    
    defer stop()
    
    <-ctx.Done()
    
    shutdown()

    This fits naturally with the rest of the Go ecosystem.

    The same context can control:

    • HTTP servers
    • database connections
    • worker goroutines
    • subprocesses

    Example:

    server.Shutdown(ctx)
    
    worker.Stop(ctx)
    
    cmd := exec.CommandContext(ctx, "worker")

    The application has one cancellation model instead of multiple unrelated shutdown mechanisms.


    11. Containers Change Process Semantics

    A Go binary running directly on a server is usually not the first process in the system.

    Inside a container, it often is:

    PID 1
    
        |
        v
    
    Go application

    PID 1 has special responsibilities on Unix systems.

    The first process in a namespace is responsible for:

    • receiving container lifecycle signals
    • adopting orphaned child processes
    • reaping terminated children

    A normal application process does not automatically behave like a full init system.

    This creates several common container problems.


    Problem 1: Signals Do Not Reach the Real Worker

    Example:

    Docker
    
     PID 1
      |
      v
     shell
      |
      v
     Go application

    The signal may reach the shell instead of the application.

    The result:

    docker stop
    
        |
        v
    
    container waits
    
        |
        v
    
    forced kill

    Avoid unnecessary shell wrappers:

    CMD ["./server"]

    is usually preferable to:

    CMD ./server

    The first starts the binary directly.


    Problem 2: Child Processes Become Zombies

    Consider:

    Go service
    
        |
        +-- worker A
        |
        +-- worker B

    If workers exit and nobody collects their status:

    worker exits
    
        |
        v
    
    zombie process

    Long-running systems can slowly accumulate these resources.

    Applications that create child processes must:

    • track them
    • call Wait()
    • define shutdown behavior

    For complex containers, a minimal init process such as tini may be appropriate.


    12. Environment Variables Are Part of Process State

    Many developers think of a process as:

    binary + arguments

    In reality:

    process state =
        executable
        +
        arguments
        +
        environment
        +
        working directory
        +
        file descriptors
        +
        permissions

    Environment inheritance is convenient.

    It is also a common source of hidden coupling.

    Example:

    A parent service has:

    AWS_ACCESS_KEY_ID
    DATABASE_PASSWORD
    DEBUG=true

    It starts a child worker.

    The worker now receives:

    AWS_ACCESS_KEY_ID
    DATABASE_PASSWORD
    DEBUG=true

    even if it never needed them.

    This can create:

    • accidental credential exposure
    • unexpected behavior changes
    • difficult debugging

    13. Control Child Environments Explicitly

    By default:

    cmd := exec.Command(
        "worker",
    )

    inherits the parent's environment.

    Sometimes that is correct.

    Sometimes it is dangerous.

    A controlled approach:

    cmd := exec.Command(
        "worker",
    )
    
    cmd.Env = []string{
        "MODE=production",
        "LOG_LEVEL=info",
    }

    Or extend intentionally:

    cmd.Env = append(
        os.Environ(),
        "WORKER_MODE=batch",
    )

    The important point is not "always remove inheritance".

    The point is:

    Environment inheritance should be an explicit design decision.


    14. Working Directory Is Also Inherited

    A child process inherits the parent's current working directory.

    This can create deployment-only failures.

    Example:

    Local development:

    /home/user/project
    
    config.yaml

    Production:

    /
    
    config.yaml missing

    The code:

    os.Open("config.yaml")

    depends on process state.

    For child processes, set the directory explicitly when required:

    cmd.Dir = "/opt/worker"

    Avoid assuming:

    working directory == executable directory

    They are independent concepts.


    15. File Descriptor Inheritance Across Processes

    File descriptors are another hidden part of process inheritance.

    A file descriptor represents an open kernel resource:

    file
    socket
    pipe
    device

    When a process starts another process, descriptors may be inherited depending on platform behavior and configuration.

    Example:

    Go server
    
    fd 3
     |
     | listening socket
     |
     +---- child process

    Now both processes may hold references to the same socket.

    This can create confusing failures:

    • server port remains occupied after shutdown
    • files cannot be removed
    • graceful restarts fail
    • resources remain alive unexpectedly

    16. Process Ownership Includes Descriptor Ownership

    A useful mental model:

    Process ownership
    
    includes:
    
        process lifetime
    
        +
    
        file descriptor lifetime
    
        +
    
        signal responsibility
    
        +
    
        cleanup responsibility

    When spawning workers, decide:

    • Should they receive stdin?
    • Should they inherit stdout/stderr?
    • Should they access network sockets?
    • Should they receive application secrets?

    Do not let inheritance answer these questions accidentally.


    17. Process Identity: Why PIDs Are Not Enough

    A process ID looks like a stable identifier:

    pid := process.Pid

    but it is only a number.

    Operating systems reuse PID values.

    Example:

    10:00
    
    worker A
    
    PID 5000
    
    
    10:05
    
    worker A exits
    
    
    10:10
    
    worker B
    
    PID 5000

    A supervisor storing only:

    PID = 5000

    cannot know whether it is talking to:

    • worker A
    • worker B

    This is a classic PID reuse problem.


    18. Native Process Handles

    Modern operating systems provide stronger process references.

    Examples:

    Linux:

    pidfd

    Windows:

    HANDLE

    These represent a specific kernel process object rather than a reusable integer.

    The difference:

    PID
    
    integer identity
    
            |
            v
    
    may be reused
    
    
    Handle
    
    kernel object reference
    
            |
            v
    
    specific process instance

    19. Go Process Handles (Process.WithHandle)

    Recent Go versions expose native process handles for low-level integrations through:

    Process.WithHandle()

    Example:

    err := process.WithHandle(
        func(handle uintptr) error {
            // platform-specific operation
            return nil
        },
    )

    The callback provides access to the underlying native handle while Go maintains the lifetime guarantee required for the operation.

    This is useful for:

    • process supervisors
    • container tooling
    • debugging systems
    • native OS integrations

    Most application code does not need this.

    A normal service should continue using:

    cmd.Start()
    
    cmd.Wait()
    
    cmd.Process.Signal()

    The handle API exists for cases where process identity itself becomes part of the system design.


    20. Keep OS Details Near the Boundary

    A common mistake in infrastructure code is allowing operating-system concepts to spread everywhere:

    business logic
    
        |
        +-- PID handling
        |
        +-- environment lookup
        |
        +-- signal processing
        |
        +-- exec.Command

    A cleaner design:

    application
    
        |
    
    interfaces
    
        |
    
    OS integration layer
    
        |
    
    kernel resources

    The business layer should describe intent:

    Run backup job
    Start worker
    Stop task

    The OS layer should implement:

    exec
    signals
    process handles
    environment

    This keeps platform complexity contained.

    10. Standard Library First, Native APIs Second

    Go intentionally keeps the operating system boundary visible.

    The standard library provides portable abstractions for the majority of production software:

    application
        |
        v
    os / os-exec / os-signal
        |
        v
    operating system

    Most services should stop here.

    A web API server usually does not need direct kernel interaction. A background worker normally does not need native process handles. A CLI tool rarely needs platform-specific system calls.

    The standard library already solves the common cases:

    os.Open()
    os.Create()
    exec.Command()
    signal.Notify()
    os.Getenv()

    These APIs are intentionally conservative. They provide predictable behavior across Linux, Windows, and macOS.

    Dropping below this layer should be a deliberate engineering decision.


    11. When golang.org/x/sys Becomes Appropriate

    golang.org/x/sys exists for cases where the operating system primitive itself matters.

    Typical examples include:

    • container runtimes;
    • security tooling;
    • high-performance networking;
    • filesystem utilities;
    • process supervisors;
    • operating-system agents.

    A typical architecture looks like:

    application logic
    
            |
    
    portable Go abstraction
    
            |
    
    platform-specific component
    
            |
    
    golang.org/x/sys
    
            |
    
    kernel API

    The important boundary is that platform-specific behavior should remain isolated.

    Avoid spreading syscall usage throughout the codebase.

    Bad:

    handler
     |
     syscall
     |
     business logic

    Better:

    handler
    
     |
    
    service interface
    
     |
    
    linux implementation
    
     |
    
    x/sys

    The rest of the application remains portable.


    12. Do Not Replace Standard APIs Without a Reason

    A common mistake in systems programming is assuming:

    Lower-level API means better performance or more professional code.

    That is usually wrong.

    For example:

    exec.Command()

    is usually preferable to manually calling process creation syscalls.

    Likewise:

    os.File

    is usually preferable to directly managing file descriptors.

    The standard library handles many details correctly:

    • platform differences;
    • error conversion;
    • resource cleanup;
    • compatibility;
    • runtime integration.

    The lower-level API is valuable only when the higher-level abstraction cannot express the required behavior.


    13. File Descriptor Ownership Across Processes

    Process creation introduces another resource ownership problem.

    When a parent process starts a child, open file descriptors may become part of the child process environment.

    This can create surprising production bugs.

    Example:

    parent
    
     |
     |
     +-- listening socket
     |
     +-- log file
     |
     +-- database connection
    
            |
            v
    
          child process

    If the child inherits descriptors that it does not need:

    • files may remain locked longer than expected;
    • ports may appear unavailable;
    • shutdown may not release resources;
    • security boundaries may be weakened.

    The rule is:

    A child process should inherit only the resources it explicitly requires.


    14. Be Explicit About Child Process Streams

    The standard os/exec package makes stream ownership visible.

    Example:

    cmd := exec.Command("worker")
    
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    
    err := cmd.Run()

    The parent explicitly decides where child output goes.

    For pipelines:

    reader, writer := io.Pipe()
    
    producer := exec.Command("producer")
    consumer := exec.Command("consumer")
    
    producer.Stdout = writer
    consumer.Stdin = reader

    The ownership model is clear:

    producer
    
        |
    
    pipe
    
        |
    
    consumer

    But every side of the pipe still needs lifecycle management.

    A blocked pipe is often a lifecycle bug, not an I/O bug.


    15. Process Groups and Service Shutdown

    Stopping one process is sometimes insufficient.

    A service may create:

    manager
    
     |
     +-- worker A
     |
     +-- worker B
     |
     +-- helper process

    Sending a signal only to the manager may leave children running.

    Production supervisors often need a process-tree strategy:

    shutdown request
    
            |
    
    identify process group
    
            |
    
    send termination signal
    
            |
    
    wait for cleanup
    
            |
    
    force kill remaining processes

    This is one reason container runtimes and service managers need deeper OS integration than ordinary applications.


    16. PID 1 Is Different

    Inside Linux containers, the first process receives PID 1.

    This process has special kernel behavior.

    A normal application running as PID 1 inherits responsibilities normally handled by an init system:

    • reaping orphaned child processes;
    • forwarding termination signals;
    • handling shutdown correctly.

    For example:

    container
    
    PID 1
     |
     +-- application
     |
     +-- worker
     |
     +-- helper

    If PID 1 ignores child cleanup:

    worker exits
    
          |
    
    zombie process remains
    
          |
    
    container accumulates dead processes

    A Go application used as PID 1 should either:

    1. deliberately implement init-like behavior;

    or

    1. run behind a minimal init process.

    The important point is that containers do not remove operating-system semantics.

    They expose them more directly.


    17. Environment Is Part of Process State

    Environment variables look simple:

    value := os.Getenv("DATABASE_URL")

    but they are inherited process state.

    A child process receives a copy of the parent's environment unless explicitly changed.

    This means:

    parent environment
    
        |
    
        v
    
    child environment

    may include:

    • credentials;
    • debug switches;
    • cloud provider variables;
    • internal service configuration.

    For production systems, treat environment inheritance as an explicit design choice.

    Example:

    cmd := exec.Command("worker")
    
    cmd.Env = []string{
        "MODE=production",
        "LOG_LEVEL=info",
    }

    The environment becomes part of the child process contract.


    18. Working Directory Is Also Process State

    A frequent deployment bug comes from assuming:

    os.Open("config.yaml")

    means:

    open the config file beside the binary.

    It actually means:

    open relative to the current working directory.

    Different environments may launch the same binary from different directories:

    developer laptop
    
            |
    
    systemd
    
            |
    
    Docker
    
            |
    
    Kubernetes

    A robust service should establish its filesystem assumptions explicitly.

    Avoid relying on inherited process state.


    19. Process Identity: PIDs Are References, Not Identity

    A PID is only a number assigned by the kernel.

    Example:

    10:00
    
    Process A
    PID 5000
    
    
    10:01
    
    Process A exits
    
    
    10:02
    
    Process B
    PID 5000

    The number stayed the same.

    The process did not.

    This matters for:

    • supervisors;
    • debuggers;
    • monitoring agents;
    • container managers.

    If the system requires stable process identity, use an operating-system handle.


    20. Process Handles and Process.WithHandle

    Modern operating systems provide stronger process references.

    Examples:

    Linux:

    pidfd

    Windows:

    HANDLE

    These represent the process object itself rather than a recyclable integer.

    Go's process APIs traditionally exposed:

    process.Pid

    For advanced system integration, newer Go versions provide access to native process handles through:

    Process.WithHandle()

    Example:

    err := process.WithHandle(func(handle uintptr) error {
    
        // platform-specific operation
    
        return nil
    })

    The purpose is narrow:

    • native process inspection;
    • service managers;
    • container tooling;
    • OS integration layers.

    It is not intended to replace ordinary process management.

    Most applications should continue using:

    cmd.Start()
    cmd.Wait()
    cmd.Process.Kill()

    21. Production Pattern: Managed Worker Process

    A typical worker manager should look conceptually like:

    manager
    
     |
     | Start()
     |
     v
    
    worker process
    
     |
     | monitor
     |
     v
    
    exit status
    
     |
     | cleanup
     |
     v
    
    restart or shutdown

    The important properties:

    • ownership is explicit;
    • shutdown is coordinated;
    • resources are released;
    • failures are observable.

    Avoid:

    go exec.Command("worker").Run()

    inside long-lived services.

    That hides ownership.


    22. Production Pattern: Graceful Shutdown

    A robust service usually follows this lifecycle:

    receive SIGTERM
    
            |
    
    stop accepting new work
    
            |
    
    finish active requests
    
            |
    
    close resources
    
            |
    
    exit

    Example:

    ctx, stop := signal.NotifyContext(
        context.Background(),
        syscall.SIGTERM,
        syscall.SIGINT,
    )
    
    defer stop()
    
    <-ctx.Done()
    
    shutdown()

    The important idea is not the signal itself.

    The important part is converting an external OS event into application lifecycle management.


    23. Production Checklist

    Before shipping Go software that interacts with the operating system:

    Process Management

    • Are all child processes owned and tracked?
    • Does every started process eventually call Wait()?
    • Are failed children detected?
    • Can the service restart safely?

    Signals

    • Does the service handle SIGTERM?
    • Are active requests allowed to finish?
    • Are resources closed during shutdown?
    • Does container termination behave correctly?

    Container Execution

    • Does the application run as PID 1?
    • Are child processes reaped?
    • Are signals forwarded correctly?
    • Is a minimal init process required?

    Environment

    • Are required variables validated at startup?
    • Are child environments controlled?
    • Could credentials leak through inheritance?

    File Descriptors

    • Are unnecessary descriptors inherited?
    • Are sockets and files closed correctly?
    • Can children keep resources alive unexpectedly?

    Process Identity

    • Is PID reuse a possible race?
    • Does the system require native process handles?
    • Should low-level operations use Process.WithHandle?

    Native APIs

    • Is x/sys actually necessary?
    • Can the standard library solve the problem?
    • Is platform-specific code isolated?

    Conclusion

    Go does not attempt to hide the operating system.

    Instead, it provides a carefully designed boundary:

    application
    
        |
    
    interfaces and policies
    
        |
    
    os / os-exec / os-signal
    
        |
    
    operating system
    
        |
    
    x/sys when necessary

    The engineering challenge is not learning every syscall.

    It is knowing where the boundary belongs.

    For most applications:

    • use os/exec instead of manual process creation;
    • use signals as lifecycle events;
    • treat child processes as owned resources;
    • avoid depending on inherited environment state;
    • keep platform-specific code isolated.

    For systems software:

    • understand PID identity;
    • manage process trees;
    • reason about file descriptor inheritance;
    • use native handles when integer identifiers are insufficient;
    • cross into x/sys only when the operating system primitive is itself part of the design.

    Reliable Go systems are built by making OS assumptions explicit.

    The operating system is always there.

    Good Go code simply chooses the right level at which to interact with it.