# How the Career-Ops Blacklist Feature Prevents Applications to Do-Not-Apply Companies

> Learn how the Career-Ops blacklist feature stops unwanted applications by filtering out do-not-apply companies, ensuring you only see relevant job postings.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: how-to-guide
- Published: 2026-08-19

---

**The Career-Ops blacklist feature prevents unwanted applications by loading your personal do-not-apply list from [`data/blacklist.md`](https://github.com/santifer/career-ops/blob/main/data/blacklist.md), normalizing company names for case-insensitive matching, and filtering matching postings during scans while blocking evaluation workflows until you explicitly override the restriction.**

Career-Ops is an open-source job application automation toolkit that helps developers manage their career search pipeline. The blacklist feature ensures you never waste time applying to companies you've previously marked as do-not-apply, operating through a multi-layered defense system that intercepts unwanted opportunities at both the scanning and evaluation stages.

## Loading and Parsing the Blacklist

The system reads your personal blacklist from [`data/blacklist.md`](https://github.com/santifer/career-ops/blob/main/data/blacklist.md), a markdown file you maintain containing a table of companies to avoid. In `scan.mjs` lines 20-30, the `loadBlacklist()` function parses this file and stores each entry as a normalized key in a JavaScript `Map`:

```javascript
// Example: Loading the blacklist (used by scan.mjs & scan-ats-full.mjs)
import { loadBlacklist } from './scan.mjs';
const blacklist = loadBlacklist();          // reads data/blacklist.md if present

```

If the file is absent, the feature remains inactive, preserving the opt-in nature of the functionality. The parser expects the markdown table structure defined in [`templates/blacklist.example.md`](https://github.com/santifer/career-ops/blob/main/templates/blacklist.example.md), extracting company names, blacklisting dates, and reasons for each entry.

## Normalizing Company Names for Reliable Matching

To ensure "Acme Corp." matches "acme corp" regardless of case or punctuation, both the blacklist parser and every scanner use the `normalizeCompany()` function (implemented in `scan.mjs` lines 92-99). This normalization happens before any comparison, preventing false negatives caused by formatting differences between job board listings and your blacklist entries.

## Automatic Filtering During Job Scans

During active scans, each posting's company name undergoes normalization and lookup against the blacklist map. According to the source code in `scan.mjs` lines 2620-2629, the behavior depends on your flags:

- **Default behavior**: Matching postings are skipped entirely and counted under `filtered_blacklist`
- **With `--include-blacklisted`**: The posting is retained but annotated with a `blacklisted` flag and the reason from your blacklist file

```javascript
// Example: Filtering offers in scan-ats-full.mjs
import { filterBlacklistedOffers } from './scan-ats-full.mjs';
const { offers, filteredBlacklist } = filterBlacklistedOffers(newOffers, blacklist, {
  includeBlacklisted: false,   // default – drop blacklisted offers
});

```

The `scan-ats-full.mjs` file reuses this same filtering logic for reverse-scan pipelines, ensuring consistency across all entry points.

## The Blacklist Gate in Evaluation Modes

Even if a posting bypasses scan filtering or is imported manually, the evaluation modes enforce a final checkpoint. Both [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 20-27) and [`modes/auto-pipeline.md`](https://github.com/santifer/career-ops/blob/main/modes/auto-pipeline.md) implement a *Blacklist gate* that triggers before Block A (the full A-G evaluation sequence).

When the gate detects a match, the workflow pauses and displays:

> "{Company} is on your blacklist (since {Since}): {Reason}. Do you still want me to evaluate this posting?"

```javascript
// Example: Blacklist gate in a mode (simplified)
if (blacklist.has(normalizeCompany(posting.company))) {
  console.log(`${posting.company} is on your blacklist – skipping evaluation.`);
  // Prompt user for override here; abort if they decline.
}

```

If you decline the override, no evaluation report, CV generation, or application submission occurs. This gate ensures that even manually imported postings from do-not-apply companies cannot accidentally enter your pipeline without explicit confirmation.

## Preserving User Control and Data Integrity

The blacklist feature maintains strict user-layer integrity. The system only reads from [`data/blacklist.md`](https://github.com/santifer/career-ops/blob/main/data/blacklist.md) and never writes to it automatically, ensuring your do-not-apply decisions remain under your complete control. This design prevents automated processes from accidentally adding companies based on temporary conditions or scan results.

## Summary

- **Storage**: Personal blacklist resides in [`data/blacklist.md`](https://github.com/santifer/career-ops/blob/main/data/blacklist.md) using a markdown table format defined in [`templates/blacklist.example.md`](https://github.com/santifer/career-ops/blob/main/templates/blacklist.example.md)
- **Normalization**: `normalizeCompany()` in `scan.mjs` ensures case-insensitive, punctuation-agnostic matching (lines 92-99)
- **Scan Filtering**: `loadBlacklist()` and `filterBlacklistedOffers()` automatically exclude matches during job scans unless `--include-blacklisted` is passed
- **Evaluation Protection**: The Blacklist gate in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) and [`modes/auto-pipeline.md`](https://github.com/santifer/career-ops/blob/main/modes/auto-pipeline.md) stops workflows before Block A evaluation
- **Override Capability**: Users can bypass restrictions via CLI flags or interactive prompts, but never accidentally

## Frequently Asked Questions

### What file format does the Career-Ops blacklist use?

The blacklist uses a markdown table format stored in [`data/blacklist.md`](https://github.com/santifer/career-ops/blob/main/data/blacklist.md). The repository provides [`templates/blacklist.example.md`](https://github.com/santifer/career-ops/blob/main/templates/blacklist.example.md) as a starter schema containing columns for company name, date added, and reason for blacklisting. The system parses this file using `loadBlacklist()` in `scan.mjs` (lines 20-30).

### How does the system handle variations in company name spelling?

Both the blacklist entries and job postings undergo normalization through `normalizeCompany()` in `scan.mjs` (lines 92-99). This function standardizes casing and removes punctuation, ensuring that "Acme Corp." matches "ACME CORP" or "acme corp" consistently.

### Can I still evaluate a company that appears on my blacklist?

Yes. While the system defaults to blocking blacklisted companies at both the scan and evaluation stages, you can override this behavior. Pass the `--include-blacklisted` flag during scans to annotate rather than filter postings, or confirm the override when the Blacklist gate prompts you during `oferta` or `auto-pipeline` modes.

### Where does the blacklist check occur in the application pipeline?

The blacklist is checked at two critical points: first during job scanning in `scan.mjs` (lines 2620-2629) and `scan-ats-full.mjs`, and second during evaluation in the modes defined in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) and [`modes/auto-pipeline.md`](https://github.com/santifer/career-ops/blob/main/modes/auto-pipeline.md). The scan filter prevents unwanted postings from entering your database, while the evaluation gate provides a final safety checkpoint before generating application materials.