The Missing Build Step: How a Single npm run build Exposed a Critical Gap in a Distributed S3 Deployment Pipeline

Introduction

In the middle of an intensive debugging and feature-implementation session for a horizontally scalable S3-compatible storage system, a single user observation — "Don't see layout fixes nor IO chart, not deployed?" — triggered a moment of architectural realization. The assistant's response, message 803, is a deceptively short acknowledgment followed by a single shell command. But beneath its brevity lies a rich story about build pipelines, deployment assumptions, and the subtle ways that development environments can diverge from production artifacts. This article unpacks that message in depth, exploring the reasoning, context, assumptions, and knowledge boundaries that surround it.

The Message

The assistant wrote:

You're right, let me check if the frontend was actually rebuilt and deployed. The Docker build copies the pre-built React app, so I need to rebuild the React app first. [bash] cd /home/theuser/gw/integrations/web/ribswebapp && npm run build 2>&1 | tail -20

This was followed by the build output showing file sizes and a note that "The build folder is ready to be deployed."

Why This Message Was Written: The Reasoning and Motivation

The immediate trigger was the user's pointed question. The user had asked for two things: renaming "SLA" to "SLO" with a 350ms threshold in the latency chart, and had observed that neither the layout fixes nor the I/O throughput chart were visible in the running web UI. The user's phrasing — "not deployed?" — was both a question and a diagnosis. They suspected that the frontend changes, despite being written to disk and committed to the Docker image build, were not actually reflected in the running containers.

The assistant's reasoning process, visible in the opening sentence, shows a rapid self-correction. The assistant had previously (in messages 775–780) written new React components: an IOThroughputChart.js, an updated ClusterTopology.js with visual role distinction, a restructured Cluster.js layout, and accompanying CSS files. These files were written to the source tree. The Docker image was rebuilt (message 781). Containers were restarted (message 782). The assistant had assumed that because the Docker build succeeded and containers restarted, the frontend changes would be live.

But the assistant then realized a critical detail: "The Docker build copies the pre-built React app." This is the moment of insight. The Dockerfile for the web UI container does not run npm run build during the Docker build. Instead, it copies static files from a pre-built directory — likely build/ — that was generated by a previous manual npm run build invocation. If the React source files were edited but the production build was not regenerated, the Docker image would still contain the old compiled JavaScript and CSS. The assistant had edited the source, rebuilt the Docker image, and restarted containers, but had never re-run the React build step. The old compiled assets were faithfully copied into the new Docker image unchanged.

This is the core motivation for the message: the assistant recognized a gap in their mental model of the deployment pipeline and took the single corrective action that was missing.

How Decisions Were Made

The decision visible in this message is straightforward but consequential. The assistant chose to run npm run build in the React webapp directory before any other diagnostic step. This was the correct minimal intervention. The assistant did not:

Assumptions Made by the User or Agent

Several assumptions operated in the background of this message.

The assistant's earlier assumption: The most significant incorrect assumption was that editing React source files and rebuilding the Docker image would automatically deploy frontend changes. This assumption stemmed from a mental model where the Docker build process was monolithic — that is, that docker build would compile everything from source. In reality, the Docker build was a two-phase process: the Go backend was compiled from source inside the Docker build (using a multi-stage build with a Go compiler), but the React frontend was treated as pre-built artifacts. The assistant had correctly internalized the Go compilation pipeline but had not updated their mental model for the frontend.

The user's assumption: The user assumed that if changes were made to the codebase and containers were restarted, the changes should appear. Their question — "not deployed?" — shows they were testing the hypothesis that the deployment step had been skipped. This was a correct diagnosis based on observable behavior: the UI still showed the old layout and lacked the I/O chart. The user's assumption that the assistant would know about the build step was reasonable, given the assistant had been the one writing the Dockerfile and build scripts.

A shared assumption: Both parties assumed that the Docker image build output (message 781) was sufficient evidence that the frontend was updated. The build output showed "exporting layers" and "naming to docker.io/library/fgw:local" — all successful. Neither party checked whether the React build had actually been re-run. This is a classic "build success ≠ deployment success" scenario.

Mistakes or Incorrect Assumptions

The primary mistake was the assistant's failure to regenerate the React production build after editing the source files. This is a common pitfall in projects with mixed build pipelines. The Go backend used a Docker multi-stage build that compiled binaries from source, creating a self-contained build process. The React frontend, however, used an external build step that produced static files outside the Docker build context. The assistant treated both as equivalent, but they were not.

A secondary issue was the lack of verification. After restarting containers (message 782), the assistant tested the backend RPC endpoints with websocat and confirmed that I/O throughput data was being tracked (message 787). But the assistant did not open the web UI in a browser to visually confirm the frontend changes. The backend verification passed, but the frontend was still serving stale assets. This is a reminder that in distributed systems with separate frontend and backend components, verification must cover both layers independently.

The assistant also did not check whether the Dockerfile's COPY instruction referenced the correct source directory. If the build/ directory was stale, the COPY would faithfully copy stale files. The assistant assumed the build/ directory was up-to-date because the source files had been edited, but npm run build is an explicit step that must be invoked.

Input Knowledge Required to Understand This Message

To fully understand message 803, a reader needs knowledge in several areas:

React build pipeline: Understanding that React applications (especially those created with Create React App, as the npm run build output suggests) require a production build step that compiles JSX, bundles modules with tools like Webpack, minifies assets, and generates a build/ directory with optimized static files. The development server (npm start) serves files from source with hot reloading, but production deployments use the compiled output.

Docker multi-stage builds and COPY semantics: The Dockerfile uses a multi-stage build where an earlier stage (named builder) compiles the Go backend, and a later stage copies artifacts from the builder stage. The React build/ directory is copied from the host filesystem (or from a previous stage) into the final image. The COPY instruction is a filesystem-level operation — it copies whatever exists at the source path at build time, without any compilation or transformation.

The project's architecture: The system has a three-layer architecture: S3 frontend proxies (stateless, port 8078), Kuri storage nodes (stateful, port 8078 internal), and a YugabyteDB database. The web UI (port 9010/9011) provides monitoring dashboards. Understanding that the web UI is a separate container with its own build lifecycle is essential.

The distinction between backend and frontend verification: The assistant had verified backend metrics via RPC calls (JSON over WebSocket), which confirmed that the Go code was correctly tracking I/O bytes. But the frontend is a separate concern — it fetches data from the same RPC endpoints but renders it via React components. Backend correctness does not guarantee frontend deployment.

Output Knowledge Created by This Message

This message created several valuable pieces of knowledge:

A corrected deployment procedure: The assistant now knows that frontend changes require an explicit npm run build step before Docker image rebuild. This knowledge is likely to be formalized in documentation or a Makefile target. The message itself serves as a record of this discovery.

Evidence of the build gap: The build output shows "201.25 kB (+686 B) build/static/js/main.84c688a0.js" — the "+686 B" indicates that the JavaScript bundle grew by 686 bytes compared to the previous build. This confirms that the new components (IOThroughputChart, updated ClusterTopology, new CSS) were successfully compiled and included. The previous Docker image would have had the old, smaller bundle.

A reproducible diagnostic pattern: The user's question "not deployed?" combined with the assistant's realization establishes a diagnostic pattern for future frontend issues: check whether the build step was executed, not just whether the Docker build succeeded.

Confirmation of the two-phase build model: The message explicitly states "The Docker build copies the pre-built React app," which documents an architectural property that might otherwise remain implicit. Anyone reading this message in the future will understand that the React build is external to the Docker build.

The Thinking Process Visible in Reasoning Parts

The assistant's reasoning is most visible in the opening sentence: "You're right, let me check if the frontend was actually rebuilt and deployed. The Docker build copies the pre-built React app, so I need to rebuild the React app first."

This sentence reveals a three-step reasoning chain:

  1. Acceptance of the user's observation: "You're right" — the assistant immediately validates the user's claim rather than defending the previous work. This is important for collaborative debugging.
  2. Hypothesis formation: "let me check if the frontend was actually rebuilt and deployed" — the assistant forms a specific hypothesis about what went wrong. The word "actually" suggests a distinction between the intended outcome and the actual outcome.
  3. Root cause identification: "The Docker build copies the pre-built React app" — the assistant identifies the mechanism that caused the hypothesis to be true. This shows an understanding of the Dockerfile's COPY semantics.
  4. Corrective action: "so I need to rebuild the React app first" — the assistant derives the necessary action from the root cause. The tail -20 choice also reveals thinking about information density. The full build output includes many lines of Webpack progress, lint warnings, and module statistics. By showing only the last 20 lines, the assistant presents the most relevant information: the final file sizes and the "ready to be deployed" confirmation. The incidental lint warning about eslint-disable-next-line is visible but not addressed, indicating a pragmatic prioritization — the build succeeded, the warning is non-blocking, and the immediate goal is deployment, not code quality. The absence of any follow-up command in this message (like restarting the container after the build) is also telling. The assistant likely intended to rebuild the Docker image and restart containers in subsequent messages. The message ends with the build output, implying "I've fixed the missing step, now I'll proceed with deployment." This is a natural pause point — show the build succeeded, then continue with the deployment pipeline.

Broader Implications

This message, while small, illustrates a universal challenge in software engineering: the gap between development and deployment. Every project with a build pipeline has a moment where someone edits a source file, runs the deployment command, and wonders why the changes aren't live. The root cause is almost always a missing intermediate step — a compilation, a bundling, a minification, a database migration, a cache clear.

In this case, the missing step was npm run build. But the pattern extends to any project where the build process is not fully automated or where the mental model of the developer does not match the actual pipeline. The solution is not just to remember to run the build step, but to automate it — perhaps by adding the npm run build command to the Dockerfile itself, so that the Docker build always compiles the frontend from source. This would eliminate the class of error entirely.

The user's role in this message is also noteworthy. The user did not have access to the build logs or the Dockerfile. They only observed the symptom: the UI was unchanged. From that symptom, they inferred the cause: "not deployed?" This is expert-level debugging — recognizing that the absence of expected changes points to a deployment failure rather than a code failure. The assistant's willingness to accept this diagnosis without defensiveness is a model of productive collaboration.

Conclusion

Message 803 is a small but perfect example of a debugging insight in action. A single user observation exposed a gap between the assistant's mental model of the deployment pipeline and its actual behavior. The assistant's response — immediate acceptance, root cause analysis, and corrective action — resolved the issue with minimal friction. The message also serves as a documentation artifact, recording the two-phase build architecture for future reference. In the broader context of the distributed S3 system being built, this moment ensured that the monitoring dashboard would actually display the I/O throughput charts and layout improvements that had been carefully implemented. Without this message, the frontend would have continued serving stale assets, and the user would have been left wondering why their feedback was ignored. The build step, invisible and often forgotten, was the missing link between code and reality.