## ℹ️ Description Automatic price reduction now works reliably during `update` runs (MODIFY mode), not only during `publish`, while staying backward compatible for existing configurations. The verify flow also explains what will happen in both modes before any command touches live ads. - Link to the related issue(s): Issue # N/A - Describe the motivation and context for this change. - Update-mode reductions needed to be explicitly opt-in so existing setups remain unchanged by default. - Pricing behavior had to stay deterministic across retries and mixed publish/update workflows. - Preview output and documentation now mirror real runtime behavior so configuration decisions are easier to trust. ## 📋 Changes Summary - Added `auto_price_reduction.on_update` (`bool`, default `false`) in model/schema so update-mode reductions are explicitly opt-in. - Refactored price reduction flow into decision + apply steps: - `evaluate_auto_price_reduction(...)` computes mode-aware outcomes - `apply_auto_price_reduction(...)` applies mutations and logs from that decision - Implemented restore-first behavior so effective reduced price is recomputed from base `price` + `price_reduction_count` before deciding a new cycle. - Updated `verify` to preview both publish and update outcomes. - Kept update-mode semantics explicit: - `delay_days` applies - `delay_reposts` is ignored in MODIFY mode - Fixed retry idempotency in `publish_ads` by restoring baseline `price` and `price_reduction_count` before each retry. - Addressed follow-up review feedback: - no-visible-change cycles advance counters (for fractional accumulation) - verify output stays non-misleading for no-visible-change cases - tightened a unit assertion from `>= 1` to `== 1` - tightened smoke behavior for `create-config` when config already exists - clarified README/docs and documented `price_reduction_count` as auto-managed - Expanded/updated tests for: - MODIFY `on_update` behavior - restore-first invariant - cross-mode interactions - retry baseline reset - verify mode previews - floor-clamped no-visible-change counter advancement Mention any dependencies, configuration changes, or additional requirements introduced. - Configuration change: new optional field `auto_price_reduction.on_update` (default `false`, backward compatible). - No external runtime dependency changes. - Existing configurations continue to work unchanged unless `on_update` is enabled. ### ⚙️ Type of Change Select the type(s) of change(s) included in this pull request: - [x] 🐞 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist Before requesting a review, confirm the following: - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * `verify` now previews automatic price-reduction outcomes for both publish and update modes. * New config option (auto_price_reduction.on_update, default false) to enable reductions during update runs. * **Documentation** * Clarified publish vs. update reduction behavior, timing rules, restore-first semantics, logging, examples, and troubleshooting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
21 KiB
Configuration Reference
Complete reference for config.yaml, the main configuration file for kleinanzeigen-bot.
Quick Start
To generate a default configuration file with all current defaults:
kleinanzeigen-bot create-config
For full JSON schema with IDE autocompletion support, see:
A reference snapshot of default values is available at docs/config.default.yaml.
To enable IDE autocompletion in config.yaml, add this at the top of the file:
# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json
For ad files, use the ad schema instead:
# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/ad.schema.json
Minimal Configuration Example
Here's the smallest viable config.yaml to get started. Only the login section is required—everything else uses sensible defaults:
# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json
# REQUIRED: Your kleinanzeigen.de credentials
login:
username: "your_username"
password: "your_password"
# OPTIONAL: Where to find your ad files (default pattern shown)
# ad_files:
# - "./**/ad_*.{json,yml,yaml}"
# OPTIONAL: Default values for all ads
# ad_defaults:
# price_type: NEGOTIABLE
# shipping_type: SHIPPING
# republication_interval: 7
Run kleinanzeigen-bot create-config to generate a complete configuration with all available options and their default values.
The ad_files setting controls where the bot looks for your ad YAML files (default pattern: ./**/ad_*.{json,yml,yaml}). The ad_defaults section lets you set default values that apply to all ads—things like price type, shipping options, and republication interval.
📖 Complete Ad Configuration Reference →
Full documentation for ad YAML files including automatic price reduction, description prefix/suffix, shipping options, category IDs, and special attributes.
File Location
The bot looks for config.yaml in the current directory by default. You can specify a different location using --config:
kleinanzeigen-bot --config /path/to/config.yaml publish
--config selects the configuration file only. Workspace behavior is controlled by installation mode (portable or xdg) and can be overridden via --workspace-mode=portable|xdg (see Installation Modes).
Valid file extensions: .json, .yaml, .yml
Configuration Structure
ad_files
Glob (wildcard) patterns to select ad configuration files. If relative paths are specified, they are relative to this configuration file.
ad_files:
- "./**/ad_*.{json,yml,yaml}"
ad_defaults
Default values for ads that can be overridden in each ad configuration file.
ad_defaults:
active: true
type: OFFER # one of: OFFER, WANTED
description_prefix: ""
description_suffix: ""
price_type: NEGOTIABLE # one of: FIXED, NEGOTIABLE, GIVE_AWAY, NOT_APPLICABLE
shipping_type: SHIPPING # one of: PICKUP, SHIPPING, NOT_APPLICABLE
# NOTE: shipping_costs and shipping_options must be configured per-ad, not as defaults
sell_directly: false # requires shipping_type SHIPPING to take effect
contact:
name: ""
street: ""
zipcode: ""
phone: "" # IMPORTANT: surround phone number with quotes to prevent removal of leading zeros
republication_interval: 7 # every X days ads should be re-published
ad_defaults.republication_intervalcontrols when ads become due for republishing.- Automatic price reductions are always evaluated during
publishruns, and they also apply duringupdateruns whenon_update: trueis set (usingdelay_daysbut ignoringdelay_reposts). - Reductions do not run in the background between runs.
- When auto price reduction is enabled, each
publish(and optionallyupdate) run logs the reduction decision. - The
verifycommand previews pricing outcomes for both publish and update modes. -v/--verboseadds a detailed reduction calculation trace.- For full behavior and examples (including timeline examples, update-mode semantics, and restore-first behavior), see AD_CONFIGURATION.md.
Tip: For current defaults of all timeout and diagnostic settings, run
kleinanzeigen-bot create-configor see the JSON schema.
categories
Additional name to category ID mappings. See the default list at: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/blob/main/src/kleinanzeigen_bot/resources/categories.yaml
categories:
Verschenken & Tauschen > Tauschen: 272/273
Verschenken & Tauschen > Verleihen: 272/274
Verschenken & Tauschen > Verschenken: 272/192
timeouts
Timeout tuning for various browser operations. Adjust these if you experience slow page loads or recurring timeouts.
timeouts:
multiplier: 1.0 # Scale all timeouts (e.g. 2.0 for slower networks)
default: 5.0 # Base timeout for web_find/web_click/etc.
page_load: 15.0 # Timeout for web_open page loads
captcha_detection: 2.0 # Timeout for captcha iframe detection
sms_verification: 4.0 # Timeout for SMS verification banners
email_verification: 4.0 # Timeout for email verification prompts
gdpr_prompt: 10.0 # Timeout when handling GDPR dialogs
login_detection: 10.0 # Timeout for DOM-based login detection (primary method)
publishing_result: 300.0 # Timeout for publishing status checks
publishing_confirmation: 20.0 # Timeout for publish confirmation redirect
image_upload: 30.0 # Timeout for image upload and server-side processing
pagination_initial: 10.0 # Timeout for first pagination lookup
pagination_follow_up: 5.0 # Timeout for subsequent pagination clicks
quick_dom: 2.0 # Generic short DOM timeout (shipping dialogs, etc.)
update_check: 10.0 # Timeout for GitHub update requests
chrome_remote_probe: 2.0 # Timeout for local remote-debugging probes
chrome_remote_debugging: 5.0 # Timeout for remote debugging API calls
chrome_binary_detection: 10.0 # Timeout for chrome --version subprocess
retry_enabled: true # Enables DOM retry/backoff when timeouts occur
retry_max_attempts: 2
retry_backoff_factor: 1.5
Timeout tuning tips:
- Slow networks or sluggish remote browsers often just need a higher
timeouts.multiplier - For truly problematic selectors, override specific keys directly under
timeouts - Keep
retry_enabledon so DOM lookups are retried with exponential backoff
For more details on timeout configuration and troubleshooting, see Browser Troubleshooting.
download
Download configuration for the download command.
download:
dir: "downloaded-ads" # default literal keeps workspace-mode download-folder behavior
# custom relative paths are resolved relative to config.yaml
include_all_matching_shipping_options: false # if true, all shipping options matching the package size will be included
excluded_shipping_options: [] # list of shipping options to exclude, e.g. ['DHL_2', 'DHL_5']
folder_name_max_length: 100 # maximum length for downloaded folder names (default: 100)
folder_name_template: "ad_{id}_{title}" # placeholders: {id}, {title}; each placeholder may appear at most once; must include {id}
ad_file_name_template: "ad_{id}" # placeholders: {id}, {title}; each placeholder may appear at most once; must include {id}
rename_existing_folders: false # if true, rename existing folders without titles to include titles (default: false)
download.dircontrols wheredownloadwrites ad files.- Leaving
download.dirat the default literaldownloaded-adskeeps workspace-mode behavior (portable workspace folder in portable mode, XDG workspace folder in XDG mode). - Custom relative
download.dirvalues are resolved relative toconfig.yaml, not the current shell working directory. - Absolute
download.dirvalues are used as-is. download.folder_name_templatecontrols downloaded ad folder names and supports{id}and{title}placeholders.download.folder_name_template: each placeholder may appear at most once.download.folder_name_templatemust include{id}so downloads from different ads cannot collide and overwrite each other.download.ad_file_name_templatecontrols the downloaded YAML file stem and image prefix; images are saved as<stem>__imgN.<ext>.download.ad_file_name_template: each placeholder may appear at most once.download.ad_file_name_templatemust include{id}so downloaded filenames remain stable and unique.download.folder_name_max_lengthlimits folder names only; downloaded file stems use a separate filename budget.
Valid templates:
ad_{id}_{title}{id}_listing{title}_{id}
Invalid templates (rejected at startup):
{id}_{id}_duplicate(repeated{id}){title}_{title}_{id}(repeated{title})
Tight-budget example (priority: {id} > literals > {title}):
- Template:
PREFIX_{id}_{title} - Input:
id=12345,title="Very Long Title",max_length=15 - Result:
PREFIX_12345_Ve(literals are preserved and{title}is truncated first)
If the configured budget is smaller than the {id} length itself, {id} is truncated as a last resort and a warning is logged.
publishing
Publishing configuration.
publishing:
delete_old_ads: "AFTER_PUBLISH" # one of: AFTER_PUBLISH, BEFORE_PUBLISH, NEVER
delete_old_ads_by_title: true # only works if delete_old_ads is set to BEFORE_PUBLISH
captcha
Captcha handling configuration. Enable automatic restart to avoid manual confirmation after captchas.
captcha:
auto_restart: true # If true, the bot aborts when a Captcha appears and retries publishing later
# If false (default), the Captcha must be solved manually to continue
restart_delay: 1h 30m # Time to wait before retrying after a Captcha was encountered (default: 6h)
browser
Browser configuration. These settings control how the bot launches and connects to Chromium-based browsers.
browser:
# See: https://peter.sh/experiments/chromium-command-line-switches/
arguments:
# Example arguments
- --disable-dev-shm-usage
- --no-sandbox
# --headless
# --start-maximized
binary_location: # path to custom browser executable, if not specified will be looked up on PATH
extensions: [] # a list of .crx extension files to be loaded
use_private_window: true
user_data_dir: "" # see https://github.com/chromium/chromium/blob/main/docs/user_data_dir.md
profile_name: ""
Common browser arguments:
--disable-dev-shm-usage- Avoids shared memory issues in Docker environments--no-sandbox- Required when running as root (not recommended)--headless- Run browser in headless mode (no GUI)--start-maximized- Start browser maximized
For detailed browser connection troubleshooting, including Chrome 136+ security requirements and remote debugging setup, see Browser Troubleshooting.
update_check
Update check configuration to automatically check for newer versions on GitHub.
update_check:
enabled: true # Enable/disable update checks
channel: latest # One of: latest, preview
interval: 7d # Check interval (e.g. 7d for 7 days)
Interval format:
s: seconds,m: minutes,h: hours,d: days- Examples:
7d(7 days),12h(12 hours),30d(30 days) - Validation: minimum 1 day, maximum 30 days
Channels:
latest: Only final releasespreview: Includes pre-releases
login
Login credentials.
login:
username: ""
password: ""
Security Note: Never commit your credentials to version control. Keep your
config.yamlsecure and exclude it from git if it contains sensitive information.
diagnostics
Diagnostics configuration for troubleshooting login detection issues and publish failures.
diagnostics:
capture_on:
login_detection: false # Capture screenshot + HTML when login detection is inconclusive
publish: false # Capture screenshot + HTML + JSON on each failed publish attempt (timeouts/protocol errors)
capture_log_copy: false # Copy entire bot log file when diagnostics are captured (may duplicate log content)
pause_on_login_detection_failure: false # Pause for manual inspection (interactive only)
timing_collection: true # Collect timeout timing data locally for troubleshooting and tuning
output_dir: "" # Custom output directory (see "Output locations (default)" below)
Migration Note:
Old diagnostics keys have been renamed/moved. Update configs and CI/automation accordingly:
login_detection_capture→capture_on.login_detectionpublish_error_capture→capture_on.publish
capture_log_copy is a new top-level flag. It may copy the same log multiple times during a single run if multiple diagnostic events are triggered.
Login Detection Behavior:
The bot uses a layered DOM-first approach to detect login status:
-
DOM check (primary method - preferred for stealth): Checks for user profile elements
- Looks for
.mr-mediumelement containing username - Falls back to
#user-emailID - Uses
login_detectiontimeout (default: 10.0 seconds) - Minimizes bot-like behavior by avoiding JSON API requests
- Looks for
-
Logged-out CTA check: Looks for login call-to-action links if logged-in markers are not found
- If login CTA selectors are matched with non-empty extracted text, detection result is logged out (
CTA_MATCH) - Note:
_has_logged_out_cta()does not explicitly verify visibility, so hidden/footer/off-canvas elements could theoretically match.
- If login CTA selectors are matched with non-empty extracted text, detection result is logged out (
-
Inconclusive fallback:
- If neither logged-in markers nor logged-out CTA are detected, result is not logged in with reason
SELECTOR_TIMEOUT
- If neither logged-in markers nor logged-out CTA are detected, result is not logged in with reason
Optional diagnostics:
- Enable
capture_on.login_detectionto capture screenshots and HTML dumps when login detection is inconclusive (SELECTOR_TIMEOUT, meaning expected selectors did not appear before timeout) - Enable
capture_on.publishto capture screenshots, HTML dumps, and JSON payloads for each failed publish attempt (e.g., attempts 1–3). - Enable
capture_log_copyto copy the entire bot log file when a diagnostic event triggers (e.g.,capture_on.publishorcapture_on.login_detection):- If multiple diagnostics trigger in the same run, the log will be copied multiple times
- Review or redact artifacts before sharing publicly
- Enable
pause_on_login_detection_failureto pause the bot for manual inspection in interactive sessions. This requirescapture_on.login_detection=true; if this is not enabled, the runtime will fail startup with a validation error. - Use custom
output_dirto specify where artifacts are saved
Output locations (default):
- Portable mode +
--config /path/to/config.yaml:/path/to/.temp/diagnostics/(portable runtime files are placed next to the selected config file) - Portable mode without
--config:./.temp/diagnostics/(current working directory) - User directories mode:
~/.cache/kleinanzeigen-bot/diagnostics/(Linux),~/Library/Caches/kleinanzeigen-bot/diagnostics/(macOS), or%LOCALAPPDATA%\kleinanzeigen-bot\Cache\diagnostics\(Windows) - Custom: Path resolved relative to your
config.yamlifoutput_diris specified
Timing collection output (default):
- Portable mode:
./.temp/timing/timing_data.json - User directories mode:
~/.cache/kleinanzeigen-bot/timing/timing_data.json(Linux) or~/Library/Caches/kleinanzeigen-bot/timing/timing_data.json(macOS) - Data is grouped by run/session and retained for 30 days via automatic cleanup during each data write
Example structure:
[
{
"session_id": "abc12345",
"command": "publish",
"started_at": "2026-02-07T10:00:00+01:00",
"ended_at": "2026-02-07T10:04:30+01:00",
"records": [
{
"operation_key": "default",
"operation_type": "web_find",
"effective_timeout_sec": 5.0,
"actual_duration_sec": 1.2,
"attempt_index": 0,
"success": true
}
]
}
]
How to read it quickly:
- Group by
commandandsession_idfirst to compare slow vs fast runs - Look for high
actual_duration_secvalues neareffective_timeout_secand repeatedsuccess: falseentries attempt_indexis zero-based (0first attempt,1first retry)- Use
operation_key+operation_typeto identify which timeout bucket (default,page_load, etc.) needs tuning - For deeper timeout tuning workflow, see Browser Troubleshooting
⚠️ PII Warning: HTML dumps, JSON payloads, timing data JSON files (for example
timing_data.json), and log copies may contain PII. Typical examples include account email, ad titles/descriptions, contact info, and prices. Log copies are produced bycapture_log_copywhen diagnostics capture runs, such ascapture_on.publishorcapture_on.login_detection. Review or redact these artifacts before sharing them publicly.
Installation Modes
On first run, when the --workspace-mode flag is not provided, the app may ask which installation mode to use. In non-interactive environments, it defaults to portable mode.
-
Portable mode (recommended for most users, especially on Windows):
- Stores config, logs, downloads, and state in the current directory
- No admin permissions required
- Easy backup/migration; works from USB drives
-
User directories mode (advanced users / multi-user setups):
- Stores files in OS-standard locations
- Cleaner directory structure; better separation from working directory
- Requires proper permissions for user data directories
OS notes:
- Windows: User directories mode uses AppData (Roaming/Local); portable keeps everything beside the
.exe. - Linux: User directories mode uses
~/.config/kleinanzeigen-bot/config.yaml,~/.local/state/kleinanzeigen-bot/, and~/.cache/kleinanzeigen-bot/; portable stays in the current working directory (for example./config.yaml,./.temp/,./downloaded-ads/). - macOS: User directories mode uses
~/Library/Application Support/kleinanzeigen-bot/config.yaml(config),~/Library/Application Support/kleinanzeigen-bot/(state/runtime), and~/Library/Caches/kleinanzeigen-bot/(cache/diagnostics); portable stays in the current directory.
Mixed footprint cleanup
If both portable and XDG footprints exist, --config without --workspace-mode is intentionally rejected to avoid silent behavior changes.
A footprint is the set of files/directories the bot creates for one mode (configuration file, runtime state/cache directories, and downloaded-ads).
Use one explicit run to choose a mode:
kleinanzeigen-bot --workspace-mode=portable --config /path/to/config.yaml verify
or
kleinanzeigen-bot --workspace-mode=xdg --config /path/to/config.yaml verify
Then remove the unused footprint directories/files to make auto-detection unambiguous for future runs.
- Remove portable footprint items in your working location:
config.yaml,.temp/(Windows:.temp\), anddownloaded-ads/(Windows:downloaded-ads\). Back up or moveconfig.yamlto your desired location before deleting it. - Remove user directories footprint items:
Linux:
~/.config/kleinanzeigen-bot/,~/.local/state/kleinanzeigen-bot/,~/.cache/kleinanzeigen-bot/. macOS:~/Library/Application Support/kleinanzeigen-bot/,~/Library/Caches/kleinanzeigen-bot/. Windows:%APPDATA%\kleinanzeigen-bot\,%LOCALAPPDATA%\kleinanzeigen-bot\,%LOCALAPPDATA%\kleinanzeigen-bot\Cache\.
Getting Current Defaults
To see all current default values, run:
kleinanzeigen-bot create-config
This generates a config file with exclude_none=True, giving you all the non-None defaults.
For the complete machine-readable reference, see the JSON schema.