<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Buffolander</title>
    <description>The latest articles on Forem by Buffolander (@buffolander).</description>
    <link>https://forem.com/buffolander</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F627914%2F88187434-099a-43df-814e-bf976d948865.png</url>
      <title>Forem: Buffolander</title>
      <link>https://forem.com/buffolander</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/buffolander"/>
    <language>en</language>
    <item>
      <title>Dealing With Dependency Vulnerabilities</title>
      <dc:creator>Buffolander</dc:creator>
      <pubDate>Sun, 31 Aug 2025 21:31:22 +0000</pubDate>
      <link>https://forem.com/buffolander/dealing-with-dependency-vulnerabilities-3pl4</link>
      <guid>https://forem.com/buffolander/dealing-with-dependency-vulnerabilities-3pl4</guid>
      <description>&lt;p&gt;Security is a fundamental aspect of software engineering, and it’s made up of multiple layers - secure coding practices, infrastructure hardening, data protection, and proactive monitoring - including a critical layer: managing the security of third-party dependencies.  &lt;/p&gt;

&lt;p&gt;Modern applications rarely exist in isolation; they are built on top of large ecosystems of open-source libraries. Each new dependency introduces both functionality and risk.&lt;/p&gt;

&lt;p&gt;Tools like GitHub’s Dependabot can automatically monitor known vulnerabilities and open pull requests to update dependencies, reducing some of the operational burden. However, automated updates are not a silver bullet. More comprehensive scanning tools, such as &lt;strong&gt;Apiiro&lt;/strong&gt;, allow organizations to detect vulnerabilities across the entire dependency graph — while still leaving engineering teams in charge of triaging, prioritizing, and patching vulnerabilities.  &lt;/p&gt;

&lt;p&gt;This article takes as practical example a &lt;strong&gt;React&lt;/strong&gt; with the &lt;strong&gt;Yarn&lt;/strong&gt; package manager, but the steps we’ll cover are &lt;strong&gt;universal practices&lt;/strong&gt;. Whether you’re working in Node.js, Python, Java, Go, or Rust, the same security posture applies: dependency hygiene, regular updates, and careful resolution of transitive risks.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Tool Discrepancies: Apiiro vs Yarn Audit
&lt;/h2&gt;

&lt;p&gt;Apiiro recently flagged &lt;strong&gt;two critical vulnerabilities&lt;/strong&gt; in a React application owned by my team.&lt;/p&gt;

&lt;p&gt;My first intuition, run yarn audit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn audit &lt;span class="nt"&gt;--groups&lt;/span&gt; dependencies &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--level&lt;/span&gt; high &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yarn reported &lt;strong&gt;over twenty critical and high vulnerabilities&lt;/strong&gt;. But where does the discrepancy in the results between each tool come from?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data sources&lt;/strong&gt;: &lt;code&gt;yarn audit&lt;/code&gt; (in Yarn Classic) relies on the npm security advisories database, whereas Apiiro aggregates multiple vulnerability databases and correlates them with application context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Severity scoring&lt;/strong&gt;: Apiiro may apply additional risk scoring or filtering rules defined by the InfoSec team, surfacing only the issues deemed most relevant or critical for the business.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;: It’s common for security teams to configure Apiiro to focus on certain severity levels or to suppress vulnerabilities that have compensating controls, whereas package manager audits usually report everything.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, the differences don’t mean one tool is “wrong”. They reflect &lt;strong&gt;different purposes&lt;/strong&gt;: Yarn’s audit is a raw report of known CVEs, while Apiiro provides a filtered, risk-based perspective.&lt;/p&gt;




&lt;h2&gt;
  
  
  An structured process
&lt;/h2&gt;

&lt;p&gt;Vulnerabilities are often flagged deep within &lt;strong&gt;nested dependencies&lt;/strong&gt;, not just at the direct dependency level. To handle them effectively, engineers need to understand their application’s &lt;strong&gt;lock file&lt;/strong&gt; and how dependency resolution works — an area often overlooked by less experienced developers.  &lt;/p&gt;

&lt;p&gt;From my perspective, the process of patching dependency vulnerabilities can be broken down into three clear steps. After each step, you should re-run your package manager’s audit command (or an external tool like Apiiro) to measure the reduction in vulnerabilities and confirm that you’re making progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Dependency Hygiene
&lt;/h3&gt;

&lt;p&gt;More often than not, unused dependencies are left dangling in applications. These may come from refactored features, copy-pasted snippets, or forgotten experiments. Regardless of the reason, each dependency added should be interpreted—through an InfoSec lens—as an &lt;strong&gt;expansion of the application’s attack surface&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Good hygiene means regularly scanning for unused dependencies and removing them. This is not only a best practice for security but also helps reduce application complexity, speed up builds, and minimize supply chain risks.  &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Updating Dependencies
&lt;/h3&gt;

&lt;p&gt;Once your dependency list is clean, the next step is to update dependencies to their latest &lt;strong&gt;non-breaking versions&lt;/strong&gt;. This is trickier than it sounds because package managers handle installation and updates differently.  &lt;/p&gt;

&lt;h4&gt;
  
  
  YARN CLASSIC (v1)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manually editing versions in &lt;code&gt;package.json&lt;/code&gt; and running &lt;code&gt;yarn install&lt;/code&gt;&lt;/strong&gt; sets the version range for the dependency but does not necessarily update transitive dependencies. The lock file may still point to older, vulnerable versions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Running &lt;code&gt;yarn upgrade {package-name}&lt;/code&gt;&lt;/strong&gt; updates the dependency to the latest version that satisfies the defined range, and rewrites the lock file accordingly.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  YARN 2+ (BERRY)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;yarn install&lt;/code&gt;&lt;/strong&gt; behaves similarly to Yarn Classic - it respects the lock file and only resolves new versions if the ranges don’t match.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;yarn up {package-name}&lt;/code&gt;&lt;/strong&gt; is the equivalent command for updating.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This difference becomes visible in the lock file. While &lt;code&gt;install&lt;/code&gt; preserves existing versions unless ranges demand otherwise, &lt;code&gt;upgrade&lt;/code&gt; (or &lt;code&gt;up&lt;/code&gt; in Yarn 2+) actively pulls in newer versions and ensures your lock file reflects those updates. Less experienced engineers often miss this nuance, leading to a false sense of security when vulnerabilities persist after a manual update.  &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Resolving Nested Dependencies
&lt;/h3&gt;

&lt;p&gt;Sometimes, even after updating direct dependencies, vulnerabilities remain because they exist as &lt;strong&gt;nested dependencies&lt;/strong&gt; one or many levels deep. In these cases, one solution is to use &lt;strong&gt;package resolutions&lt;/strong&gt;. It instructs Yarn to use a specific resolution (specific package version) instead of anything the resolver would normally pick.   &lt;/p&gt;

&lt;p&gt;The property &lt;code&gt;resolutions&lt;/code&gt; is defined at the root of &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resolutions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lodash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.17.21"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous example forces Yarn to use a single version of &lt;code&gt;lodash&lt;/code&gt; across the entire dependency graph, regardless of which direct dependency introduced it.&lt;/p&gt;

&lt;p&gt;Another approach is being laser focused and scope resolutions to a specific dependency paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resolutions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react-scripts/**/lodash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.17.21"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating your application's resolutions run &lt;code&gt;yarn install&lt;/code&gt; to update the dependency resolutions in your lock file.&lt;/p&gt;




&lt;p&gt;Done! At this point your application should have been adequately patched up, unless no patch is yet available for one of the compromised nested dependencies.&lt;/p&gt;

&lt;p&gt;Such cases should be communicated to InfoSec, ideally including alternatives to replace the compromised library and the respective engineering effort to refactor the application. It would be on them to weight the vulnerability risk, understand wether the compromised dependency is actively maintained and how often security patches are released, and if a refactor would be indeed required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Dependency management is often treated as a background task — until a vulnerability surfaces and suddenly becomes a production risk. By following a structured approach — &lt;strong&gt;hygiene&lt;/strong&gt;, &lt;strong&gt;updates&lt;/strong&gt;, and &lt;strong&gt;resolutions&lt;/strong&gt; — engineering teams can take control of their software supply chain and meaningfully reduce their exposure.&lt;/p&gt;

&lt;p&gt;Automated tools like Dependabot and Apiiro are powerful allies, but ultimately, engineers must understand the nuances of their dependency graph, lock files, and package manager behaviors. With this knowledge, vulnerabilities stop being chaotic surprises and become manageable parts of a continuous security process.&lt;/p&gt;

</description>
      <category>infosec</category>
      <category>vulnerabilities</category>
      <category>node</category>
    </item>
    <item>
      <title>Building Robust Error Handling in FastAPI – and avoiding rookie mistakes</title>
      <dc:creator>Buffolander</dc:creator>
      <pubDate>Sat, 30 Aug 2025 16:42:03 +0000</pubDate>
      <link>https://forem.com/buffolander/building-robust-error-handling-in-fastapi-and-avoiding-rookie-mistakes-ifg</link>
      <guid>https://forem.com/buffolander/building-robust-error-handling-in-fastapi-and-avoiding-rookie-mistakes-ifg</guid>
      <description>&lt;p&gt;FastAPI is an amazing web framework for building HTTP-based services in Python and very accessible to newcomers. Which to some extent might encourage some to skip the documentation in favour of speed – or at least an illusion of it.&lt;/p&gt;

&lt;p&gt;When I first started building with FastAPI, I made my fair share of mistakes when it comes to error handling – stack traces would leak into JSON responses because my service was missing a catch-all handler, or I’d raise FastAPI HTTPExceptions deep inside my service layer, mixing presentation details with business logic. The result was leaky abstractions and inconsistent error payloads.&lt;/p&gt;

&lt;p&gt;If you’ve ever found yourself in that same boat, this article is for you. Let’s walk through how to design a clean, maintainable exception-handling strategy in FastAPI — one that separates concerns, keeps your clients happy, and generates friendly and predictable errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Structured Error Handling Matters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; clients can rely on a predictable error schema no matter which endpoint they hit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintainability:&lt;/strong&gt; your service code stays framework-agnostic — ready for reusing in background jobs or CLI tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt; you never accidentally leak internal messages or stack traces to end users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability:&lt;/strong&gt; every unexpected crash ends up in your logs with full context, making root-cause analysis a breeze.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding Application Layers
&lt;/h2&gt;

&lt;p&gt;Before jumping into implementation and framework-specific details, let's zoom out and look at the typical layers in a backend service. This should help us understand where to raise different types of errors and how they're picked u[ and transformed into HTTP responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentation Layer
&lt;/h3&gt;

&lt;p&gt;This is where requests are received from and responses are returned to clients. Also referred to as &lt;strong&gt;controller layer&lt;/strong&gt; in FastAPI this is represented by routes and the application object.&lt;/p&gt;

&lt;p&gt;This layer is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Parsing request parameters (body, query string, path parameters).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Returning HTTP responses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Catching exceptions and formatting them for clients.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In FastAPI, Pydantic is responsible for handling request parameters validation.&lt;/p&gt;

&lt;p&gt;Validation exceptions are handled internally by the framework. Custom exception handlers are expected to be implemented on this layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service or (Business Domain) Layer
&lt;/h3&gt;

&lt;p&gt;This is where your core application logic live. It should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Framework-agnostic (no FastAPI imports).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focused on domain logic, e.g. fetching data, enforcing rules, triggering workflows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;strong&gt;custom exceptions&lt;/strong&gt; are raised, like &lt;code&gt;ItemNotFoundError&lt;/code&gt;. These should be descriptive and carry metadata (status code, error code, message) but &lt;strong&gt;never&lt;/strong&gt; return HTTP responses directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure Layer
&lt;/h3&gt;

&lt;p&gt;This layer interacts with the outside world, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;File systems and blob storage&lt;/li&gt;
&lt;li&gt;Queues and notifications&lt;/li&gt;
&lt;li&gt;Internal and third-party APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Catch and wrap external exceptions into domain-specific ones. For example, if a database query fails because the item doesn’t exist, raise &lt;code&gt;ItemNotFoundError&lt;/code&gt; instead of leaking a raw SQL exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pydantic vs. Domain Errors
&lt;/h2&gt;

&lt;p&gt;FastAPI (via Pydantic) already nails request validation – missing fields, wrong types, malformed JSON. All those cases get seamlessly turned into nice &lt;code&gt;422 Unprocessable Content&lt;/code&gt; responses, but there’s a big gap left:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Syntactically valid&lt;/strong&gt; requests that just can’t be processed in your application domain (due to violation of business rules), lookup failures, rate limits, authentications and authorization checks — these are on &lt;strong&gt;you&lt;/strong&gt; to handle.&lt;/p&gt;

&lt;p&gt;That’s where custom exceptions and global handlers come into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crafting Custom Exceptions
&lt;/h2&gt;

&lt;p&gt;Define a lightweight base exception that carries all the info you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# exceptions.py
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppBaseException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error_code&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build specific cases on top of &lt;code&gt;AppBaseException&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# exceptions.py
&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ItemNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppBaseException&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Item &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item_not_found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuotaExceededError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppBaseException&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API quota exceeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quota_exceeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These exceptions live in your business logic layer. They know nothing about FastAPI’s HTTP mechanisms, which makes them perfectly reusable elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping to HTTP Responses with Handlers
&lt;/h2&gt;

&lt;p&gt;Instead of raising HTTPException deeply within your services, prefer catching custom exceptions at the application level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# exception_handlers.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traceback&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JSONResponse&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AppBaseException&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_exception_handlers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@app.exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppBaseException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;app_exc_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AppBaseException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@app.exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;catch_all_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UnhandledException:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;traceback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__traceback__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal_server_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Something went wrong. Please try again later.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The first handler cleanly transforms known domain errors into the right status code and JSON shape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second one is your catch-all. Any unexpected &lt;code&gt;NameError&lt;/code&gt;, &lt;code&gt;ValueError&lt;/code&gt;, or database hiccup will still produce a 500 with a generic message and log the full traceback.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wiring It Up
&lt;/h2&gt;

&lt;p&gt;Here’s how the pieces glue together in a minimal FastAPI app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# services.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ItemNotFoundError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QuotaExceededError&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This is just a dummy service layer.

    In an actual application it should be importing a DB or
    a third-party API client, for example.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ItemNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;777&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;QuotaExceededError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sample Item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# routes.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.services&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_item&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items/{item_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# main.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.exception_handlers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;register_exception_handlers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.routes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;  &lt;span class="c1"&gt;# your APIRouter with endpoints
&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;register_exception_handlers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Logging &amp;amp; Observability
&lt;/h2&gt;

&lt;p&gt;A global error handler without logging is like a fire alarm you can’t hear. Make sure you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use a structured logger (e.g., JSON output) so you can filter by error_code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Include a request or correlation ID in every log line for tracing – that should be handled in a middleware and is out of the scope of this article.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ship errors to an observability service like Sentry, Datadog, or AWS CloudWatch where your monitors can trigger alerts in case of anomalies.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;By keeping your exceptions domain-focused and your handlers HTTP-focused, you’ll end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cleaner, more testable service code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistent error payloads for every endpoint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A safety net that catches the unexpected&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secure APIs that never spill internal details&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with this skeleton the next time you spin up a FastAPI project. You (and your on-call teammates) will thank yourself when that 2 AM pager alert finally goes silent.&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>errors</category>
    </item>
    <item>
      <title>The Thin Line Between Psychological Safety and the Comfort Zone</title>
      <dc:creator>Buffolander</dc:creator>
      <pubDate>Wed, 16 Apr 2025 09:35:52 +0000</pubDate>
      <link>https://forem.com/buffolander/the-thin-line-between-psychological-safety-and-underperformance-22ig</link>
      <guid>https://forem.com/buffolander/the-thin-line-between-psychological-safety-and-underperformance-22ig</guid>
      <description>&lt;p&gt;When I began my professional life a long time ago (on the business side, not in software engineering), feedback used to be direct. If you made a mistake, you'd hear about it - often bluntly - and were expected to improve. There was no buffer between performance and critique.&lt;/p&gt;

&lt;p&gt;Fast forward to today, and it feels like the pendulum has swung in the opposite direction. Now, criticism is often seen as a threat to psychological safety, and even well-meant feedback can be interpreted as damaging or watered down to such extent it does not generate any improvement.&lt;/p&gt;

&lt;p&gt;This shift has raised a difficult but necessary question: &lt;strong&gt;have we gone too far in protecting psychological safety at the expense of performance or is it something else we're doing wrong?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; This isn't a critique of psychological safety - it's a call to reclaim its actual meaning.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Where did psychological safety come from?
&lt;/h2&gt;

&lt;p&gt;The concept of psychological safety was introduced by Harvard professor &lt;strong&gt;Amy Edmondson&lt;/strong&gt; in 1999. Her research on hospital teams revealed that those who reported more mistakes weren't underperforming - they were simply more transparent. They felt safe enough to speak up, admit failure, and collaborate without fear.&lt;/p&gt;

&lt;p&gt;The idea gained widespread traction in 2015 through &lt;strong&gt;Google's Project Aristotle&lt;/strong&gt;, which found that psychological safety was the top trait of high-performing teams. This kicked off a wave of company culture initiatives focused on creating safer, more inclusive environments.&lt;/p&gt;

&lt;p&gt;Since then, psychological safety has become a buzzword in every modern workplace. From HR workshops to engineering offsites, we've been taught that creating a "safe space" is the path to innovation, collaboration, and long-term performance. And to a degree, this is absolutely true.&lt;/p&gt;

&lt;p&gt;But there's a catch - &lt;strong&gt;psychological safety doesn't mean the absence of discomfort&lt;/strong&gt;. It doesn't mean avoiding criticism. And it certainly doesn't mean lowering the bar.&lt;/p&gt;




&lt;h2&gt;
  
  
  The danger of misinterpretation
&lt;/h2&gt;

&lt;p&gt;The problem isn't with psychological safety itself - it's with how it's often misunderstood and misapplied. When teams adopt the language of safety without balancing it with &lt;strong&gt;clear standards&lt;/strong&gt;, &lt;strong&gt;accountability&lt;/strong&gt;, and &lt;strong&gt;candid feedback&lt;/strong&gt;, the result is what Amy Edmondson calls the &lt;strong&gt;Comfort Zone&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's break down her &lt;strong&gt;learning zone model&lt;/strong&gt;, which illustrates this dynamic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Standards.High          Standards.Low
Safety.High   Learning zone (ideal)   Comfort zone (complacency) 
Safety.Low    Anxiety zone (fear)     Apathy zone (disinterest)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;strong&gt;Learning Zone&lt;/strong&gt;, people feel safe to speak up and are held to a high bar. Mistakes are seen as opportunities to improve, but not as acceptable outcomes in themselves. It's a productive tension - people are both supported and challenged.&lt;/p&gt;

&lt;p&gt;But many teams today fall into the &lt;strong&gt;Comfort Zone&lt;/strong&gt;, where psychological safety is mistaken for emotional comfort, and where giving direct feedback is seen as a threat to morale. Here, the language of empathy becomes a shield for mediocrity. Difficult conversations are avoided. Underperformance lingers. High performers get frustrated.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the research actually says
&lt;/h2&gt;

&lt;p&gt;Several studies have reinforced the value of psychological safety - but always in the context of &lt;strong&gt;clear expectations&lt;/strong&gt; and &lt;strong&gt;performance standards&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amy Edmondson&lt;/strong&gt;, in her book &lt;em&gt;The Fearless Organization&lt;/em&gt;, emphasizes that psychological safety is not about being nice or lowering the bar. It's about creating a space where people can speak up - and then be expected to deliver.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google's Project Aristotle&lt;/strong&gt; identified psychological safety as critical, but it was only one of several key traits. Others included &lt;em&gt;dependability, structure and clarity, and impact of work&lt;/em&gt;. In other words, safety mattered - but so did discipline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A report from McKinsey in 2021&lt;/strong&gt; found that teams with both high psychological safety and clear accountability outperformed all others. Teams with only one of the two (e.g., high safety but low standards) showed mixed or even negative results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In &lt;strong&gt;Charles Duhigg&lt;/strong&gt;'s book, &lt;em&gt;Smarter Faster Better&lt;/em&gt;, high-performing teams are described as having both trust and tension. They allow disagreement, but tie it back to action. They value reflection, but also execution.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;These studies consistently show that psychological safety is a performance enabler - but only when paired with strong leadership, clarity, and accountability.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What it looks like when it goes wrong
&lt;/h2&gt;

&lt;p&gt;The consequences of getting this balance wrong are visible in many modern teams. In environments where &lt;em&gt;psychological safety&lt;/em&gt; is emphasised without enforcing high standards, teams may appear harmonious on the surface - but underneath they're stalling. The gap between psychological safety and performance isn't just theoretical - it shows up in team dynamics every day.&lt;/p&gt;

&lt;p&gt;In many modern engineering teams, especially those that prioritise flat hierarchies and consensus-driven cultures, the line between support and accountability can get blurry. Conversations meant to drive clarity are postponed to "protect morale."&lt;/p&gt;

&lt;p&gt;Critical feedback is softened or skipped entirely in the name of being "supportive." The intention is good, but the result is often a team that's emotionally safe and operationally stagnant.&lt;/p&gt;

&lt;p&gt;In Agile squads, this can show up as endless retrospectives with little follow-through, or sprint planning sessions where no one challenges vague or unrealistic tickets. Code reviews drift into rubber-stamping. Performance issues linger without being addressed directly, and over time, mediocrity becomes normalised.&lt;/p&gt;

&lt;p&gt;Meanwhile, high performers begin to disengage. They want feedback, clarity, and challenge - but when those things are missing, motivation fades. Worse, when everyone is treated the same regardless of their output or the quality of their work, it sends a clear message: performance doesn't really matter here.&lt;/p&gt;

&lt;p&gt;All of this contributes to a culture where &lt;strong&gt;psychological safety turns into emotional insulation&lt;/strong&gt;. Difficult conversations are seen as threats rather than opportunities. The team may feel cohesive, but not driven. Safe, but not ambitious.&lt;/p&gt;

&lt;p&gt;In that comfort zone, teams stop growing.&lt;/p&gt;

&lt;p&gt;This isn't psychological safety - it's quiet decline, disguised as empathy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reclaiming the balance: Safety + Standards
&lt;/h2&gt;

&lt;p&gt;So how do we walk the tightrope? How can leaders, engineering managers, and ICs foster psychological safety without lowering the bar?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redefine Psychological Safety&lt;/strong&gt;&lt;br&gt;
Remind your team that psychological safety means being safe to speak up, not safe from feedback. You don't build safety by avoiding conflict - you build it by handling conflict well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set Explicit Standards&lt;/strong&gt;&lt;br&gt;
Be clear about what "good" looks like - code quality, project management, architecture design, delivery timelines, ownership expectations, etc. When standards are vague, people default to their comfort zone, they never seek fixing their skill gaps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make Feedback Normal, Not Exceptional&lt;/strong&gt;&lt;br&gt;
Create a rhythm for feedback - weekly one-on-ones, retro rituals, code reviews. Feedback shouldn't be an event; it should be a habit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Coach Through Discomfort&lt;/strong&gt;&lt;br&gt;
When people struggle, don't rush to protect them from the pain of failure. Instead, walk with them through it. Growth often starts where comfort ends.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hold the Line&lt;/strong&gt;&lt;br&gt;
Don't let poor performance slide under the guise of empathy. Respect people enough to tell them the truth about how they're doing - and trust them enough to believe they can improve.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  A false binary
&lt;/h1&gt;

&lt;p&gt;One of the biggest challenges today is the false binary between kindness and rigour, empathy and accountability. Leaders are often made to feel that they must choose: protect people's emotions or raise the bar.&lt;/p&gt;

&lt;p&gt;But great leadership doesn't live at either extreme. It lives in the ability to &lt;strong&gt;care deeply and challenge directly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's telling someone they missed the mark - and still believing in them. It's holding someone accountable while investing in their growth. That's how trust is built - not through avoidance, but through honesty.&lt;/p&gt;

&lt;p&gt;We're living in a workplace culture that values empathy - and that's a good thing. But empathy without standards leads to &lt;strong&gt;pity&lt;/strong&gt;, not progress. And safety without accountability breeds &lt;strong&gt;comfort&lt;/strong&gt;, not competence.&lt;/p&gt;

&lt;p&gt;The real goal isn't to avoid discomfort - it's to create an environment where discomfort leads to growth. That's the Learning Zone. That's where high-trust, high-performance teams live.&lt;/p&gt;

&lt;p&gt;So let's stop seeing psychological safety as the absence of criticism, and start seeing it as the presence of &lt;strong&gt;trust&lt;/strong&gt;, &lt;strong&gt;honesty&lt;/strong&gt;, and &lt;strong&gt;shared ambition&lt;/strong&gt;. Because the teams that grow the most aren't the ones that feel safe &lt;em&gt;all the time&lt;/em&gt; - they're the ones that feel safe enough to be challenged.&lt;/p&gt;

&lt;p&gt;That's the thin line, and learning to walk it well certainly is an important leadership skill to be developed.&lt;/p&gt;

</description>
      <category>management</category>
      <category>psychologicalsafety</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
