diff --git a/src/input/InputStream.hxx b/src/input/InputStream.hxx
index 26dae48ed..b38cc67df 100644
--- a/src/input/InputStream.hxx
+++ b/src/input/InputStream.hxx
@@ -80,6 +80,7 @@ private:
 
 public:
 	template<typename U>
+	[[nodiscard]]
 	InputStream(U &&_uri, Mutex &_mutex) noexcept
 		:uri(std::forward<U>(_uri)),
 		 mutex(_mutex)
@@ -109,12 +110,14 @@ public:
 	 * notifications
 	 * @return an #InputStream object on success
 	 */
+	[[nodiscard]]
 	static InputStreamPtr Open(const char *uri, Mutex &mutex);
 
 	/**
 	 * Just like Open(), but waits for the stream to become ready.
 	 * It is a wrapper for Open(), WaitReady() and Check().
 	 */
+	[[nodiscard]]
 	static InputStreamPtr OpenReady(const char *uri, Mutex &mutex);
 
 	/**
@@ -131,6 +134,7 @@ public:
 	 *
 	 * The caller must lock the mutex.
 	 */
+	[[nodiscard]]
 	InputStreamHandler *ExchangeHandler(InputStreamHandler *new_handler) noexcept {
 		return std::exchange(handler, new_handler);
 	}
@@ -140,10 +144,12 @@ public:
 	 *
 	 * No lock necessary for this method.
 	 */
+	[[nodiscard]]
 	const char *GetURI() const noexcept {
 		return uri.c_str();
 	}
 
+	[[nodiscard]]
 	std::string_view GetUriView() const noexcept {
 		return uri;
 	}
@@ -168,18 +174,19 @@ public:
 	 *
 	 * The caller must lock the mutex.
 	 */
+	[[nodiscard]]
 	bool IsReady() const {
 		return ready;
 	}
 
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	bool HasMimeType() const noexcept {
 		assert(ready);
 
 		return !mime.empty();
 	}
 
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	const char *GetMimeType() const noexcept {
 		assert(ready);
 
@@ -203,14 +210,14 @@ public:
 		mime = std::move(_mime);
 	}
 
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	bool KnownSize() const noexcept {
 		assert(ready);
 
 		return size != UNKNOWN_SIZE;
 	}
 
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	offset_type GetSize() const noexcept {
 		assert(ready);
 		assert(KnownSize());
@@ -224,14 +231,14 @@ public:
 		offset += delta;
 	}
 
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	offset_type GetOffset() const noexcept {
 		assert(ready);
 
 		return offset;
 	}
 
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	offset_type GetRest() const noexcept {
 		assert(ready);
 		assert(KnownSize());
@@ -239,7 +246,7 @@ public:
 		return size - offset;
 	}
 
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	bool IsSeekable() const noexcept {
 		assert(ready);
 
@@ -249,7 +256,7 @@ public:
 	/**
 	 * Determines whether seeking is cheap.  This is true for local files.
 	 */
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	bool CheapSeeking() const noexcept;
 
 	/**
@@ -301,14 +308,14 @@ public:
 	 *
 	 * The caller must lock the mutex.
 	 */
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	virtual bool IsEOF() const noexcept = 0;
 
 	/**
 	 * Wrapper for IsEOF() which locks and unlocks the mutex; the
 	 * caller must not be holding it already.
 	 */
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	bool LockIsEOF() const noexcept;
 
 	/**
@@ -319,12 +326,14 @@ public:
 	 * @return a tag object or nullptr if the tag has not changed
 	 * since the last call
 	 */
+	[[nodiscard]]
 	virtual std::unique_ptr<Tag> ReadTag() noexcept;
 
 	/**
 	 * Wrapper for ReadTag() which locks and unlocks the mutex;
 	 * the caller must not be holding it already.
 	 */
+	[[nodiscard]]
 	std::unique_ptr<Tag> LockReadTag() noexcept;
 
 	/**
@@ -334,7 +343,7 @@ public:
 	 *
 	 * The caller must lock the mutex.
 	 */
-	[[gnu::pure]]
+	[[nodiscard]] [[gnu::pure]]
 	virtual bool IsAvailable() const noexcept;
 
 	/**
@@ -351,6 +360,7 @@ public:
 	 * @param size the maximum number of bytes to read
 	 * @return the number of bytes read
 	 */
+	[[nodiscard]]
 	virtual std::size_t Read(std::unique_lock<Mutex> &lock,
 				 std::span<std::byte> dest) = 0;
 
@@ -360,6 +370,7 @@ public:
 	 *
 	 * Throws std::runtime_error on error.
 	 */
+	[[nodiscard]]
 	std::size_t LockRead(std::span<std::byte> dest);
 
 	/**
@@ -397,6 +408,7 @@ class ScopeExchangeInputStreamHandler {
 	InputStreamHandler *const old_handler;
 
 public:
+	[[nodiscard]]
 	ScopeExchangeInputStreamHandler(InputStream &_is,
 					InputStreamHandler *new_handler) noexcept
 		:is(_is), old_handler(is.ExchangeHandler(new_handler)) {}